# JFrog Artifactory Pro 7.161.20 on localhost — Setup Guide

A single-node JFrog Artifactory Pro instance running in containers on this host,
with all state persisted on disk under `/work/acb/jfrog`.

**Current status on this host:** running and healthy. Artifactory 7.161.20 is
reachable at <http://localhost:8082/ui/>, all 13 microservices report `HEALTHY`,
and PostgreSQL 17 is backing it. **No license is installed**, so repository
read/write is blocked until you apply one — see [section 6](#6-licensing--required-before-you-can-store-artifacts).

---

## 1. What gets deployed

| Component | Image | Container | Role |
|---|---|---|---|
| Artifactory Pro | `releases-docker.jfrog.io/jfrog/artifactory-pro:7.161.20` | `artifactory` | The repository manager (all microservices in one container) |
| PostgreSQL | `docker.io/library/postgres:17-alpine` | `jfrog-postgres` | Backing database — **mandatory**, see note below |

Both containers share the podman network `jfrog-net`, so Artifactory reaches the
database at the hostname `jfrog-postgres`.

> ### Important: Derby is gone
> Older Artifactory 7.x guides tell you to run the image on its own — it would fall
> back to a bundled Apache Derby database. **7.161.x removes that fallback.** Starting
> the image without a PostgreSQL server fails during boot with:
>
> ```
> java.lang.IllegalStateException: Cannot start the application with a
> database other than PostgreSQL. For more information, see JFrog documentation
> ```
>
> PostgreSQL is therefore part of the minimum setup, not an optional upgrade.

---

## 2. Host requirements

- `podman` (or Docker — every command below works with `docker` substituted)
- ~10 GB free disk for the image plus the data directory
- ~4 GB RAM available to the Artifactory container
- Outbound HTTPS to `releases-docker.jfrog.io` for the image pull
- TCP ports `8081` and `8082` free on loopback

---

## 3. Directory layout

`JFROG_HOME` is `/work/acb/jfrog`:

```
/work/acb/jfrog
├── .env                        # DB credentials (chmod 600)
├── start-artifactory.sh        # bring the stack up
├── stop-artifactory.sh         # bring it down, keeping data
├── GUIDE.md                    # this file
├── postgresql/data/            # PostgreSQL data directory
└── artifactory/var/            # Artifactory home, bind-mounted to
    │                           #   /var/opt/jfrog/artifactory in the container
    ├── etc/system.yaml         # main configuration (DB connection, node id)
    ├── etc/security/           # master.key, join.key — back these up
    ├── data/                   # binary store: the actual artifacts
    ├── bootstrap/              # first-boot seed content
    ├── log/                    # service logs
    ├── backup/                 # scheduled backups land here
    └── work/                   # transient working files
```

Everything Artifactory persists lives in `artifactory/var`. Deleting that directory
resets the instance; keeping it (plus the PostgreSQL data directory) preserves it.

---

## 4. Setup from scratch

### 4.1 Create the directories

The container runs as uid/gid **1030**, so `var` must be writable by it:

```bash
export JFROG_HOME=/work/acb/jfrog
mkdir -p $JFROG_HOME/artifactory/var/etc
mkdir -p $JFROG_HOME/postgresql/data
chown -R 1030:1030 $JFROG_HOME/artifactory/var
chmod -R 777 $JFROG_HOME/artifactory/var
chown -R 999:999 $JFROG_HOME/postgresql/data      # postgres uid in the alpine image
```

### 4.2 Download the image

```bash
podman pull releases-docker.jfrog.io/jfrog/artifactory-pro:7.161.20
```

No JFrog credentials are needed — `releases-docker.jfrog.io` serves this image
anonymously. It is roughly **4.4 GB** on disk.

### 4.3 Store the database credentials

```bash
cat > $JFROG_HOME/.env <<EOF
POSTGRES_DB=artifactory
POSTGRES_USER=artifactory
POSTGRES_PASSWORD=$(head -c 18 /dev/urandom | base64 | tr -d '/+=' | head -c 20)
EOF
chmod 600 $JFROG_HOME/.env
```

### 4.4 Write `system.yaml`

`$JFROG_HOME/artifactory/var/etc/system.yaml` — substitute the values from `.env`:

```yaml
shared:
  node:
    id: "artifactory-localhost"
  database:
    type: postgresql
    driver: org.postgresql.Driver
    url: "jdbc:postgresql://jfrog-postgres:5432/artifactory"
    username: "artifactory"
    password: "<the password from .env>"
```

The PostgreSQL JDBC driver ships inside the Artifactory image; you do not add one.

### 4.5 Start the stack

```bash
$JFROG_HOME/start-artifactory.sh
```

The script creates the `jfrog-net` network, starts PostgreSQL, waits for
`pg_isready`, then starts Artifactory with:

```bash
podman run -d --name artifactory --network jfrog-net --restart unless-stopped \
  -v "$JFROG_HOME/artifactory/var:/var/opt/jfrog/artifactory:Z" \
  -p 127.0.0.1:8081:8081 \
  -p 127.0.0.1:8082:8082 \
  --ulimit nofile=32000:32000 \
  releases-docker.jfrog.io/jfrog/artifactory-pro:7.161.20
```

Notes on the flags:
- `:Z` relabels the bind mount for SELinux — required on RHEL-family hosts.
- Ports are bound to `127.0.0.1` so the instance is reachable only from this host.
- `--ulimit nofile=32000:32000` is JFrog's documented minimum file-descriptor limit.

### 4.6 Wait for it to come up

First boot creates the schema and runs every migration — allow **3–6 minutes**.

```bash
# poll until healthy
until [ "$(curl -s -o /dev/null -w '%{http_code}' \
  http://localhost:8082/router/api/v1/system/health)" = "200" ]; do sleep 10; done
echo ready
```

Follow along with `podman logs -f artifactory`.

---

## 5. Verifying the instance

All of the following were run against this deployment and returned the results shown.

```bash
# router health - expect "state": "HEALTHY" for all 13 services
curl -s http://localhost:8082/router/api/v1/system/health

# Artifactory ping -> OK
curl -s http://localhost:8081/artifactory/api/system/ping

# version -> {"version":"7.161.20","revision":"86120900",...}
curl -su admin:password http://localhost:8081/artifactory/api/system/version

# UI -> 200
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8082/ui/
```

The required service set that must all report `HEALTHY`:
`jfrt jfac jfmd jffe jfob jfcfg jfcon jfevt jfevd jftpl jfomr jfbus jfmelt`

Then open **http://localhost:8082/ui/** in a browser. First login is
`admin` / `password`; the setup wizard forces a password reset, then asks for a
base URL (`http://localhost:8082`), a proxy (skip), and a license.

---

## 6. Licensing — required before you can store artifacts

**Artifactory Pro does not serve repositories without a license key.** The
instance boots, reports healthy, and serves the UI and the system APIs, but every
repository operation is refused:

```bash
$ curl -su admin:password -T ./file.txt \
    http://localhost:8081/artifactory/example-repo-local/file.txt
{ "errors" : [ { "status" : 503, "message" : "No license installed!" } ] }

$ curl -su admin:password -X PUT \
    http://localhost:8081/artifactory/api/repositories/my-repo -d '...'
{ "errors" : [ { "status" : 400, "message" :
  "This REST API is available only in Artifactory Pro ... make sure your
   server is activated with a valid license key." } ] }
```

`curl -su admin:password http://localhost:8081/artifactory/api/system/license`
currently returns `{"type":"N/A","validThrough":"","licensedTo":""}`.

To make the instance usable, pick one:

1. **Apply an existing Pro license** — UI: *Administration → License Management*,
   or via API:
   ```bash
   curl -su admin:<password> -X POST \
     http://localhost:8081/artifactory/api/system/licenses \
     -H 'Content-Type: application/json' \
     -d '{"licenseKey":"<your key>"}'
   ```
2. **Request a free trial key** at <https://jfrog.com/start-free/> and apply it
   the same way.
3. **Switch to the OSS edition** if Pro features are not needed — identical setup,
   no key required, just change the image:
   ```
   releases-docker.jfrog.io/jfrog/artifactory-oss:7.161.20
   ```

Note that a few repositories (`example-repo-local`, `artifactory-build-info`,
`auto-trashcan`) already exist — they are created by the first-boot bootstrap, not
by you, and they still cannot be read or written until a license is applied.

The repeated `jfconnect` / `jcs.jfrog.io` telemetry errors in the log are this
unlicensed instance failing to phone home. They are harmless and stop once a
license is applied.

---

## 7. Day-to-day operation

```bash
# start / stop (data preserved)
/work/acb/jfrog/start-artifactory.sh
/work/acb/jfrog/stop-artifactory.sh

# logs
podman logs -f artifactory
tail -f /work/acb/jfrog/artifactory/var/log/artifactory-service.log

# status
podman ps --filter name=artifactory --filter name=jfrog-postgres

# open a psql shell
source /work/acb/jfrog/.env
podman exec -it jfrog-postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"
```

### Using it as a registry

Once a license is applied (section 6), create a repository in the UI, then:

```bash
# generic upload
curl -u admin:<password> -T ./file.tgz \
  http://localhost:8081/artifactory/example-repo-local/file.tgz

# docker (needs a Docker repo + a reverse proxy or subdomain config)
podman login localhost:8082
```

---

## 8. Backup and reset

**Back up** — stop the stack first for a consistent snapshot:

```bash
/work/acb/jfrog/stop-artifactory.sh
tar czf jfrog-backup-$(date +%F).tar.gz -C /work/acb jfrog
```

At minimum keep `artifactory/var/etc/security/master.key` — without it the
encrypted values in the database cannot be read.

**Full reset** — destroys everything:

```bash
podman rm -f artifactory jfrog-postgres
rm -rf /work/acb/jfrog/artifactory/var /work/acb/jfrog/postgresql/data
```

Then start again from step 4.1.

---

## 9. Troubleshooting

| Symptom | Cause / fix |
|---|---|
| `Cannot start the application with a database other than PostgreSQL` | No PostgreSQL, or `system.yaml` not read. Confirm the file is at `var/etc/system.yaml`, owned by `1030`, and that `jfrog-postgres` is running on `jfrog-net`. |
| `Master key is missing. Pending for N seconds` | Normal during the first 30–60 s of boot while the key is generated. Only a problem if it hits the 5-minute timeout. |
| `The Access service is not ready` | Normal mid-boot. Access starts before the rest. |
| `Failed to push event(s) ... jcs.jfrog.io` | Benign telemetry failure; no license configured. |
| `503 "No license installed!"` on any repository path | Expected on unlicensed Pro. Apply a license or switch to the OSS image — see section 6. |
| Health endpoint returns `503` | Router is up, some services are not. Keep waiting; check `podman logs`. |
| `StopSignal SIGTERM failed to stop container ... resorting to SIGKILL` | Podman's default 10 s grace period is too short for Artifactory. Stop with `podman stop -t 90 artifactory` (what `stop-artifactory.sh` does). |
| Permission-denied errors writing to `var` | Ownership drifted. Re-run `chown -R 1030:1030` on `artifactory/var`. |
| Port already in use | Change the left-hand side of `-p 127.0.0.1:8082:8082` and set `artifactory.baseUrl` accordingly. |
