Self-host
Self-hosting quickstart
Run a private Denju registry from the published server image, using either bundled or existing PostgreSQL and S3-compatible storage.
Every Denju release publishes the production server as a multi-architecture container image:
ghcr.io/amxv/denju-server:vX.Y.Z
ghcr.io/amxv/denju-server:latest
That is the same denju-server product used by the official registry. You do not need a Rust toolchain or a local server build to self-host Denju.
The server needs two durable services behind it:
- PostgreSQL for identities, resources, relationships, current refs, and registry state.
- S3-compatible object storage for immutable skill content and release snapshots.
Self-hosting has one deployment model: run the published Denju server image. The only choice is where PostgreSQL and S3-compatible storage come from.
If you want the reference stack to provide those dependencies, deploy/compose.yml runs the published image alongside PostgreSQL 18 and Garage. If you already have PostgreSQL and S3-compatible storage, run the same image on your container platform and point it at those services. See Operations.
Requirements for the reference stack
- Docker with Compose support.
- The
deploy/directory from a Denju source checkout. - For remote clients: an HTTPS hostname for the registry and an HTTPS object-store origin reachable by those clients.
For a same-machine trial, the bundled loopback defaults work without TLS.
1. Create the environment file
From the repository root:
cp deploy/self-host.env.example deploy/self-host.env
The example uses ghcr.io/amxv/denju-server:latest, which is convenient for a trial. For a production deployment, change DENJU_SERVER_IMAGE to an exact release such as ghcr.io/amxv/denju-server:vX.Y.Z so upgrades happen only when you choose them.
Generate strong values for every blank secret. This snippet prints compatible values you can paste into the file:
printf 'DENJU_DB_OWNER_PASSWORD=%s\n' "$(openssl rand -hex 24)"
printf 'DENJU_DB_APP_PASSWORD=%s\n' "$(openssl rand -hex 24)"
printf 'DENJU_DB_WORKER_PASSWORD=%s\n' "$(openssl rand -hex 24)"
printf 'DENJU_GARAGE_RPC_SECRET=%s\n' "$(openssl rand -hex 32)"
printf 'DENJU_S3_ACCESS_KEY_ID=GK%s\n' "$(openssl rand -hex 10)"
printf 'DENJU_S3_SECRET_ACCESS_KEY=%s\n' "$(openssl rand -hex 32)"
printf 'DENJU_RECOVERY_TOKEN=%s\n' "$(openssl rand -hex 32)"
Protect the finished file because it contains registry credentials:
chmod 600 deploy/self-host.env
For a same-host trial, keep the example values for:
DENJU_PUBLIC_URL=http://127.0.0.1:7788
DENJU_PORT=7788
DENJU_S3_PORT=3900
DENJU_S3_PRESIGN_ENDPOINT=http://127.0.0.1:3900
2. Pull and start the stack
docker compose \
--env-file deploy/self-host.env \
-f deploy/compose.yml \
pull
docker compose \
--env-file deploy/self-host.env \
-f deploy/compose.yml \
up -d
The Compose stack pulls the published Denju server image, starts PostgreSQL and Garage, runs database migrations once with the same image, then starts the registry. It does not compile Denju from source.
Check the services:
docker compose \
--env-file deploy/self-host.env \
-f deploy/compose.yml \
ps
3. Verify the registry
The reference local registry exposes:
http://127.0.0.1:7788/health/live
http://127.0.0.1:7788/health/ready
http://127.0.0.1:7788/health/metrics
Verify readiness:
curl -fsS http://127.0.0.1:7788/health/ready
The server also includes an object-store conformance check. Run it inside the server container so it uses the same configured provider boundary:
docker compose \
--env-file deploy/self-host.env \
-f deploy/compose.yml \
exec server denju-server check-object-store
4. Connect a Denju client
On the same host:
denju setup --registry http://127.0.0.1:7788
A production client should instead use your HTTPS registry origin:
denju setup --registry https://denju.example.com
A Denju installation is bound to one registry in v1, so choose the intended registry when setting up each machine.
5. Put TLS in front for remote users
The reference Compose file binds the registry and Garage to loopback. That is deliberate. For remote access, put your normal reverse proxy or load balancer in front of them.
A typical deployment exposes:
https://denju.example.com -> Denju server
https://objects.example.com -> Garage S3 endpoint
Then set:
DENJU_PUBLIC_URL=https://denju.example.com
DENJU_S3_PRESIGN_ENDPOINT=https://objects.example.com
DENJU_S3_PRESIGN_ENDPOINT matters because Denju gives clients short-lived signed upload/download URLs. That origin must be reachable by the clients, not only by the server container.
Upgrade the reference stack
For a production registry, change DENJU_SERVER_IMAGE to the next exact release, then pull and recreate the migration/server services:
docker compose \
--env-file deploy/self-host.env \
-f deploy/compose.yml \
pull migrate server
docker compose \
--env-file deploy/self-host.env \
-f deploy/compose.yml \
up -d
The one-shot migration service must succeed before the new server becomes ready. Keeping an exact image tag makes rollback and change control straightforward.
Next: Configuration covers managed PostgreSQL/S3 and every important server setting. Operations covers running the image directly, migrations, health, recovery, and operator quarantine.