DevOpsil
88%
Needs Review

JFrog Artifactory: Setup, Repository Configuration, and Pipeline Integration

Sarah ChenSarah Chen5 min read

Artifactory vs Nexus

Both Artifactory and Nexus are universal artifact repositories. The key differences:

  • Artifactory has broader ecosystem integration (Artifactory CLI, JFrog Pipelines, Xray)
  • Artifactory supports 30+ package types natively, including Conan (C++) and Conda
  • JFrog Cloud (SaaS) is a mature option; Nexus doesn't have a comparable hosted offering
  • Xray (JFrog's security scanner) integrates deeply with Artifactory at the artifact level

Artifactory's free OSS tier covers most use cases. Pro and Enterprise add features like replication, access federation, and release lifecycle management.


Installation

# docker-compose.yml
version: '3'
services:
  artifactory:
    image: releases-docker.jfrog.io/jfrog/artifactory-oss:latest
    container_name: artifactory
    restart: unless-stopped
    ports:
      - "8081:8081"    # Artifactory UI
      - "8082:8082"    # Artifactory access
    volumes:
      - /opt/artifactory/data:/var/opt/jfrog/artifactory
    environment:
      - JF_ROUTER_ENTRYPOINTS_EXTERNALPORT=8082
    ulimits:
      nproc: 65535
      nofile:
        soft: 32000
        hard: 40000
mkdir -p /opt/artifactory/data
chown -R 1030:1030 /opt/artifactory/data   # UID 1030 = artifactory user

docker-compose up -d

Access at http://your-server:8082/ui. Default credentials: admin / password (change immediately).

Kubernetes via Helm

helm repo add jfrog https://charts.jfrog.io
helm repo update

helm upgrade --install artifactory jfrog/artifactory \
  --namespace artifactory \
  --create-namespace \
  --set artifactory.persistence.size=100Gi \
  --set postgresql.postgresqlPassword=strong_password

Repository Types

Artifactory has three repository types (same concept as Nexus):

TypePurpose
LocalStore artifacts you produce
RemoteProxy and cache an external repository
VirtualAggregate local + remote under one URL

Creating Repositories via REST API

# Create a local Docker repository
curl -u admin:password -X PUT \
  "http://artifactory.example.com:8082/artifactory/api/repositories/docker-local" \
  -H "Content-Type: application/json" \
  -d '{
    "rclass": "local",
    "packageType": "docker",
    "description": "Internal Docker images"
  }'

# Create a remote (proxy) for Docker Hub
curl -u admin:password -X PUT \
  "http://artifactory.example.com:8082/artifactory/api/repositories/docker-hub-remote" \
  -H "Content-Type: application/json" \
  -d '{
    "rclass": "remote",
    "packageType": "docker",
    "url": "https://registry-1.docker.io/",
    "description": "Docker Hub proxy"
  }'

# Create a virtual combining both
curl -u admin:password -X PUT \
  "http://artifactory.example.com:8082/artifactory/api/repositories/docker" \
  -H "Content-Type: application/json" \
  -d '{
    "rclass": "virtual",
    "packageType": "docker",
    "repositories": ["docker-local", "docker-hub-remote"],
    "defaultDeploymentRepo": "docker-local"
  }'

Docker Registry

Configure Docker to use Artifactory:

# Login (using the virtual repo)
docker login artifactory.example.com:8082 -u developer -p yourpassword

# Tag and push
docker tag myapp:latest artifactory.example.com:8082/docker-local/myapp:latest
docker push artifactory.example.com:8082/docker-local/myapp:latest

# Pull (routes through virtual repo — checks local first, then remote)
docker pull artifactory.example.com:8082/docker/nginx:alpine

For HTTP (non-SSL dev environments):

{
  "insecure-registries": ["artifactory.example.com:8082"]
}

Maven Integration

Configure ~/.m2/settings.xml:

<settings>
  <mirrors>
    <mirror>
      <id>central</id>
      <mirrorOf>*</mirrorOf>
      <url>http://artifactory.example.com:8082/artifactory/maven-virtual/</url>
    </mirror>
  </mirrors>
  <servers>
    <server>
      <id>central</id>
      <username>developer</username>
      <password>yourpassword</password>
    </server>
    <server>
      <id>snapshots</id>
      <username>developer</username>
      <password>yourpassword</password>
    </server>
  </servers>
</settings>

pom.xml:

<distributionManagement>
  <repository>
    <id>central</id>
    <name>releases</name>
    <url>http://artifactory.example.com:8082/artifactory/libs-release-local/</url>
  </repository>
  <snapshotRepository>
    <id>snapshots</id>
    <name>snapshots</name>
    <url>http://artifactory.example.com:8082/artifactory/libs-snapshot-local/</url>
  </snapshotRepository>
</distributionManagement>

JFrog CLI: The Preferred Integration Method

The JFrog CLI (jf) handles authentication, publishing, build info, and promotion:

# Install
curl -fL https://install-cli.jfrog.io | sh

# Configure
jf config add myserver \
  --url http://artifactory.example.com:8082 \
  --user admin \
  --password yourpassword

# Upload artifacts
jf rt upload "target/*.jar" libs-release-local/com/example/myapp/1.0.0/

# Download artifacts
jf rt download libs-release-local/com/example/myapp/1.0.0/ ./downloads/

# Publish build info (links artifacts to CI build)
jf rt build-publish myapp-build 42

# Promote a build from staging to production
jf rt build-promote myapp-build 42 libs-production-local \
  --source-repo libs-staging-local \
  --status Deployed \
  --comment "QA approved"

CLI in GitHub Actions

- name: Setup JFrog CLI
  uses: jfrog/setup-jfrog-cli@v4
  env:
    JF_URL: ${{ secrets.ARTIFACTORY_URL }}
    JF_ACCESS_TOKEN: ${{ secrets.ARTIFACTORY_TOKEN }}

- name: Build and publish
  run: |
    jf mvn clean deploy
    jf rt build-publish ${{ github.repository }} ${{ github.run_number }}

Artifactory Query Language (AQL)

AQL is how you search Artifactory programmatically — useful for finding artifacts, checking what's deployed, or building promotion logic:

# Find all Docker images tagged "latest" newer than 7 days
curl -u admin:password \
  -H "Content-Type: text/plain" \
  -d 'items.find({
    "repo": "docker-local",
    "name": {"$match": "manifest.json"},
    "created": {"$gt": "2026-03-25T00:00:00.000Z"}
  }).include("name", "repo", "path", "created", "size")' \
  http://artifactory.example.com:8082/artifactory/api/search/aql

# Find artifacts never downloaded (orphan cleanup candidates)
curl -u admin:password \
  -H "Content-Type: text/plain" \
  -d 'items.find({
    "repo": "libs-snapshot-local",
    "stat.downloads": {"$eq": null},
    "created": {"$lt": "2025-10-01T00:00:00.000Z"}
  }).include("name", "repo", "path", "created")' \
  http://artifactory.example.com:8082/artifactory/api/search/aql

Access Control

Artifactory uses Permission Targets → Groups → Users:

# Create a group via REST API
curl -u admin:password -X PUT \
  "http://artifactory.example.com:8082/artifactory/api/security/groups/developers" \
  -H "Content-Type: application/json" \
  -d '{"name":"developers","description":"Developer team"}'

# Create permission target (read on all repos for developers)
curl -u admin:password -X PUT \
  "http://artifactory.example.com:8082/artifactory/api/security/permissions/developer-read" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "developer-read",
    "repositories": ["ANY"],
    "principals": {
      "groups": {
        "developers": ["r", "n"]
      }
    }
  }'

Privilege codes: r=read, n=annotate, d=delete, w=deploy/cache, m=manage, managedXrayMeta=Xray metadata.


Cleanup Policies

# Delete artifacts not downloaded in 90 days from snapshots repo
jf rt delete "libs-snapshot-local/*" \
  --spec '{"files":[{"aql":"items.find({\"repo\":\"libs-snapshot-local\",\"stat.downloads\":{\"$eq\":null},\"created\":{\"$lt\":\"relative_to=-90d\"}})"}]}'

# Or use the built-in Artifactory Cleanup job in the UI:
# Admin → Maintenance → Cleanup → Create cleanup policy
Share:

Was this article helpful?

Sarah Chen
Sarah Chen

CI/CD Engineering Lead

Automation evangelist who believes no deployment should require a human. I write pipelines, break pipelines, and write about both. Code-first, always.

Related Articles

Discussion