Compose provides two team deployment modes. Both modes use the same OCI image and private HTTP service.
| Mode | Application processes | Records | Artifact files | Use this mode when |
|---|---|---|---|---|
| Compact Compose | Exactly one | SQLite | One file volume | One server owns all durable data. |
| External-storage Compose | One or more | Existing PostgreSQL | Existing S3-compatible storage | Your team operates the durable providers separately. |
Choose a mode
Use Compact Compose for the shortest one-server team installation. This mode does not provide application failover or horizontal scaling.
Use external-storage Compose for multiple application processes. This mode requires PostgreSQL and supported S3-compatible storage before startup.
Artifact Server does not implement transfer from compact storage to external storage. Select the durable storage model before production use.
Meet the shared requirements
Both modes require these items:
- Docker with Docker Compose.
- An immutable Artifact Server image digest for the release.
- One HTTPS application origin, such as
https://artifacts.example.com. - One separate registrable content domain, such as
content.example.net. - Wildcard DNS and TLS for
*.content.example.net. - One OIDC or WorkOS browser-login provider.
- A trusted reverse proxy or gateway.
The package does not install DNS, certificates, a reverse proxy, PostgreSQL, or object storage.
Prepare the server
Copy the Compose package from the source checkout. Then work from the copied directory.
cp -R packaging/compose /srv/artifact-server
cd /srv/artifact-server
cp .env.example .envEdit .env. Set the shared configuration:
ARTIFACT_SERVER_IMAGE=ghcr.io/plannotator/artifact-server@sha256:replace_with_release_digest
ARTIFACT_SERVER_BIND_ADDRESS=127.0.0.1
ARTIFACT_SERVER_PORT=8787
ARTIFACT_SERVER_ORIGIN=https://artifacts.example.com
ARTIFACT_SERVER_CONTENT_DOMAIN=content.example.net
ARTIFACT_SERVER_READINESS_WITHDRAWAL_MS=1000
ARTIFACT_SERVER_SHUTDOWN_DEADLINE_MS=10000
ARTIFACT_SERVER_REQUEST_LOG_SAMPLE_RATE=0.01The application origin and content domain must have different registrable domains. This rule keeps untrusted artifact content outside the trusted application origin.
Configure browser login
Configure exactly one browser-login provider. A partial configuration or two provider families cause startup to fail.
OIDC
Register this redirect URI with the OIDC provider:
https://artifacts.example.com/auth/callbackSet these values in .env:
ARTIFACT_SERVER_OIDC_ISSUER=https://idp.example.com/realms/main
ARTIFACT_SERVER_OIDC_CLIENT_ID=artifact-server
ARTIFACT_SERVER_OIDC_SCOPES=openid email profileThe client secret is optional for a public client that uses PKCE. A confidential client requires one secret source.
Use ARTIFACT_SERVER_OIDC_CLIENT_SECRET_FILE for a mounted secret file. Add a private Compose override that mounts this file read-only.
WorkOS
Set the exact AuthKit issuer and client ID:
ARTIFACT_SERVER_WORKOS_ISSUER=https://replace.authkit.app
ARTIFACT_SERVER_WORKOS_CLIENT_ID=client_replace_meWorkOS also requires ARTIFACT_SERVER_WORKOS_API_KEY_FILE. Add a private Compose override that mounts this file read-only.
Use one dedicated WorkOS environment for each Artifact Server environment.
Configure the reverse proxy
Route the application hostname and all wildcard content hosts to port 8787. Preserve the requested host and HTTPS protocol.
The proxy must support streamed uploads, response streaming, required timeouts, and the configured upload limits.
Keep the default loopback bind for one replica behind a host-level proxy. Attach a network gateway for multiple external-storage replicas.
Caddy, Nginx, Traefik, HAProxy, or an existing gateway can provide this boundary. The Compose package does not select one.
Public artifact access removes the Artifact Server login check. It does not make a private server reachable from the internet.
Run Compact Compose
Compact Compose stores SQLite, artifact files, staged uploads, and generated secrets in the artifact-server-data volume.
Verify that the server has space for the active volume, one backup copy, and expected growth.
Verify the Compose configuration
docker compose configInitialize the data volume
docker compose run --rm --no-deps artifact-server \
init --admin-email admin@example.com \
--data /var/lib/artifact-server/dataThe command creates the installation identity and machine API credential. It prints only the installation ID and data directory.
Start the server
docker compose up --detach --waitVerify liveness and readiness
curl --fail http://127.0.0.1:8787/health
curl --fail http://127.0.0.1:8787/readyThen open the configured HTTPS application origin through the reverse proxy.
Do not scale the compact service. The fixed container name makes a second writer fail during Compose startup.
The volume needs reliable SQLite locking and atomic replacement. NFS-like filesystems do not have qualification for this mode.
Back up Compact Compose
Run the included backup script with a new target directory:
./compact-backup.sh /srv/backups/artifact-server/2026-09-02The script stops the service before it copies data. Then it writes these files:
data.tardata.tar.sha256support-manifest.json
The script restarts the service after a successful copy. A failed backup retains an INCOMPLETE marker.
Restore Compact Compose
Stop the target service. Use an empty target volume and a new Compose project name for a recovery drill.
export COMPOSE_PROJECT_NAME=artifact-server-restored
./compact-restore.sh /srv/backups/artifact-server/2026-09-02
docker compose up --detach --waitThe restore script verifies the checksum, archive paths, filesystem entries, and complete installation integrity.
The script rejects running targets, nonempty volumes, incomplete backups, unsafe paths, links, special files, and checksum failures.
If you restore the same installation, keep the application origin and content domain unchanged.
Verify the installation ID, projects, artifacts, versions, actions, access configuration, manifests, and exact file bytes.
Run external-storage Compose
External-storage Compose uses the base file and compose.external-storage.yaml. The override removes the compact volume and fixed container name.
Provide these dependencies before startup:
- One PostgreSQL database for this installation.
- One AWS S3 or Cloudflare R2 bucket.
- One gateway on the Compose network for multiple replicas.
- Enough database connections for every replica and migration process.
Another S3-compatible provider requires successful Artifact Server adapter contract tests. Provider compatibility text alone is not qualification.
MinIO is an integration-test provider. The production package does not install or recommend MinIO.
Create the required secret files
Create the secret directory and machine API credential:
install -d -o 1000 -g 1000 -m 0700 /etc/artifact-server
printf 'as_key_key_%s_%s\n' "$(openssl rand -hex 16)" "$(openssl rand -hex 32)" \
> /etc/artifact-server/api-token
chown 1000:1000 /etc/artifact-server/api-token
chmod 0400 /etc/artifact-server/api-tokenWrite the complete PostgreSQL URL to its secret file:
printf '%s\n' 'postgresql://artifactserver:replace_me@postgres.example/artifactserver' \
> /etc/artifact-server/database-url
chown 1000:1000 /etc/artifact-server/database-url
chmod 0400 /etc/artifact-server/database-urlThe image runs with UID and GID 1000:1000. Each mounted secret file must use this ownership and mode 0400.
Do not put the machine API credential or PostgreSQL URL in .env.
Configure the external providers
Add these values to .env:
ARTIFACT_SERVER_INSTALLATION_ID=artifact-server-production
ARTIFACT_SERVER_BOOTSTRAP_ADMIN_EMAIL=admin@example.com
ARTIFACT_SERVER_API_TOKEN_SECRET_FILE=/etc/artifact-server/api-token
ARTIFACT_SERVER_DATABASE_URL_SECRET_FILE=/etc/artifact-server/database-url
ARTIFACT_SERVER_POSTGRES_MAX_CONNECTIONS=10
ARTIFACT_SERVER_S3_BUCKET=replace-me
ARTIFACT_SERVER_S3_REGION=us-east-1
ARTIFACT_SERVER_S3_FORCE_PATH_STYLE=falseKeep ARTIFACT_SERVER_INSTALLATION_ID stable for the complete installation lifetime.
AWS installations can use an instance role or another AWS workload identity. Do not add static S3 keys for that configuration.
For R2 or another qualified provider, copy the static-credential overlay:
cp compose.external-storage.s3-credentials.yaml.example \
compose.external-storage.s3-credentials.yamlCreate both S3 credential files with UID and GID 1000:1000. Set each file mode to 0400.
Set these configuration values for the private overlay:
ARTIFACT_SERVER_S3_ENDPOINT=https://replace-me.r2.cloudflarestorage.com
ARTIFACT_SERVER_S3_ACCESS_KEY_ID_SECRET_FILE=/etc/artifact-server/s3-access-key-id
ARTIFACT_SERVER_S3_SECRET_ACCESS_KEY_SECRET_FILE=/etc/artifact-server/s3-secret-access-keyIf you use static credentials, add --file compose.external-storage.s3-credentials.yaml to every external-storage command.
Verify the configuration and migrate PostgreSQL
Parse the complete configuration and connect to both providers:
docker compose --file compose.yaml --file compose.external-storage.yaml \
run --rm --no-deps artifact-server \
config check --mode external-storageVerify the current schema:
docker compose --file compose.yaml --file compose.external-storage.yaml \
run --rm --no-deps artifact-server migrate statusApply the release migrations:
docker compose --file compose.yaml --file compose.external-storage.yaml \
run --rm --no-deps artifact-server migrate applyServing containers verify the schema. They never apply migrations during startup.
Start one replica
Use the loopback overlay for one replica behind a host-level reverse proxy:
docker compose --file compose.yaml \
--file compose.external-storage.yaml \
--file compose.external-storage.local-port.yaml \
up --detach --waitVerify the local process:
curl --fail http://127.0.0.1:8787/health
curl --fail http://127.0.0.1:8787/readyDo not combine the fixed local-port overlay with multiple replicas.
Start multiple replicas
Attach the reverse proxy or gateway to the Compose network. Then start the replicas:
docker compose --file compose.yaml --file compose.external-storage.yaml \
up --detach --scale artifact-server=2 --waitRoute both HTTPS host families to the artifact-server service on port 8787.
Set ARTIFACT_SERVER_POSTGRES_MAX_CONNECTIONS from the PostgreSQL connection budget. Each replica uses its own bounded pool.
The package does not configure autoscaling, load balancing, or multi-region failover.
Back up external storage
PostgreSQL and the complete Artifact Server object prefix form one backup unit. A database-only or bucket-only backup is incomplete.
Stop every application replica
docker compose --file compose.yaml --file compose.external-storage.yaml \
stop artifact-serverBack up PostgreSQL
Create one transaction-consistent logical PostgreSQL backup with the provider procedure.
Back up object storage
Copy the complete Artifact Server installation prefix with the provider procedure.
Record the support manifest
docker compose --file compose.yaml --file compose.external-storage.yaml \
run --rm --no-deps artifact-server \
support manifest --mode external-storageStore this output and checksums with both backup parts.
Verify a restore
Restore both parts into empty providers. Then run the integrity command:
docker compose --file compose.yaml --file compose.external-storage.yaml \
run --rm --no-deps artifact-server \
integrity check --mode external-storageStart the application only after the integrity report has status healthy.
Update the application image
Cross-version update and rollback compatibility does not have a release qualification yet. Do not change versions without release-specific compatibility instructions.
When release instructions permit an update, use this sequence:
Create and verify a backup
Use the backup procedure for the selected storage mode.
Set the new image digest
Replace ARTIFACT_SERVER_IMAGE with the exact digest from the release instructions.
Verify the configuration
Run docker compose config for compact mode. Run config check for external-storage mode.
Apply permitted migrations
For external-storage mode, run migrate status. Then run migrate apply.
Replace the application containers
Run docker compose up --detach --force-recreate --wait with the files for the selected mode.
Verify the installation
Verify /health, /ready, browser sign-in, one private artifact, and one public artifact.
The prior image must accept the current schema before rollback. Artifact Server does not apply an automatic down-migration.
Remove the Compose application
Stop and remove compact application containers while you preserve the data volume:
docker compose downStop and remove external-storage application containers with both Compose files:
docker compose --file compose.yaml --file compose.external-storage.yaml downThe external command does not remove PostgreSQL or object storage. Those providers are outside the Compose package.
Known limits
- Compact Compose supports exactly one application process.
- Compact Compose does not support NFS-like data volumes without separate qualification.
- External-storage Compose does not install or back up its durable providers.
- External-storage Compose does not configure autoscaling or multi-region failover.
- The package does not configure a reverse proxy, DNS, TLS, or firewall rules.
- Cross-version update and rollback compatibility does not have qualification.
- Compact-to-external-storage transfer is not implemented.