forked from artifact-keeper/artifact-keeper
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
462 lines (449 loc) · 20 KB
/
Copy pathdocker-compose.yml
File metadata and controls
462 lines (449 loc) · 20 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# =============================================================================
# Artifact Keeper - Getting Started Compose File
# =============================================================================
#
# This file is a STARTING POINT for running Artifact Keeper locally or
# evaluating it in a non-production setting. It is NOT a production-ready
# deployment. Credentials are hardcoded, volumes use the local driver, and
# no resource limits or redundancy are configured.
#
# For production deployments, use this as a reference and adapt it to your
# environment: swap in managed databases, external object storage, real TLS
# certificates, secrets management, and appropriate resource constraints.
# See: https://artifactkeeper.com/docs/deployment/
#
# Container images are available from two registries:
# ghcr.io (default): ghcr.io/artifact-keeper/artifact-keeper-{backend,web,openscap}
# Docker Hub: artifactkeeper/{backend,web,openscap}
#
# To use Docker Hub instead, replace image references below. For example:
# ghcr.io/artifact-keeper/artifact-keeper-backend:${ARTIFACT_KEEPER_VERSION:-latest}
# -> artifactkeeper/backend:${ARTIFACT_KEEPER_VERSION:-latest}
#
# Pin a specific release by setting ARTIFACT_KEEPER_VERSION in a .env file
# alongside this compose file (e.g. ARTIFACT_KEEPER_VERSION=1.1.0). Omit the
# "v" prefix. If unset, "latest" is used.
# =============================================================================
services:
# ---------------------------------------------------------------------------
# PostgreSQL TLS certificates (init container)
# ---------------------------------------------------------------------------
# Generates self-signed certs so the backend connects to Postgres over TLS.
# In production, replace this with certs issued by your internal CA or use a
# managed database service that handles TLS for you.
pg-certs:
image: alpine:3.23
container_name: artifact-keeper-pg-certs
entrypoint: ["/bin/sh", "-c"]
command:
- |
if [ -f /certs/server.crt ]; then
echo "pg-certs: certificates already exist, skipping"
exit 0
fi
apk add --no-cache openssl >/dev/null 2>&1
echo "pg-certs: generating self-signed certificates..."
openssl genrsa -out /certs/ca.key 4096
openssl req -x509 -new -nodes -key /certs/ca.key -sha256 -days 365 \
-out /certs/ca.crt -subj "/CN=Artifact Keeper CA/O=Artifact Keeper/C=US"
openssl genrsa -out /certs/server.key 2048
openssl req -new -key /certs/server.key \
-out /certs/server.csr -subj "/CN=postgres/O=Artifact Keeper/C=US"
cat > /certs/server.ext <<EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage=digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment
subjectAltName=DNS:localhost,DNS:postgres,IP:127.0.0.1
EOF
openssl x509 -req -in /certs/server.csr -CA /certs/ca.crt -CAkey /certs/ca.key \
-CAcreateserial -out /certs/server.crt -days 365 -sha256 -extfile /certs/server.ext
rm -f /certs/server.csr /certs/server.ext /certs/ca.srl
chown 70:70 /certs/server.key /certs/server.crt /certs/ca.crt
chmod 600 /certs/server.key
chmod 644 /certs/server.crt /certs/ca.crt
echo "pg-certs: done"
volumes:
- pg_certs:/certs
restart: "no"
# ---------------------------------------------------------------------------
# PostgreSQL
# ---------------------------------------------------------------------------
# Stores all metadata (artifacts, repositories, users, tokens, etc.).
# In production, consider a managed Postgres service (RDS, Cloud SQL, etc.)
# with proper backups, replication, and connection pooling.
# Default credentials below are for local evaluation only.
postgres:
image: postgres:16-alpine
container_name: artifact-keeper-db
depends_on:
pg-certs:
condition: service_completed_successfully
environment:
POSTGRES_USER: registry
POSTGRES_PASSWORD: registry
POSTGRES_DB: artifact_registry
volumes:
- postgres_data:/var/lib/postgresql/data
- ./docker/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro,z
- ./docker/init-pg-ssl.sh:/docker-entrypoint-initdb.d/init-pg-ssl.sh:ro,z
- pg_certs:/var/lib/postgresql/certs:ro
ports:
- "30432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U registry -d artifact_registry"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
# ---------------------------------------------------------------------------
# Dependency-Track bootstrap (init container)
# ---------------------------------------------------------------------------
# Changes the default Dependency-Track password and extracts an API key so
# the backend can submit SBOMs automatically. Writes the key to a shared
# volume that the backend reads at startup.
# Gated behind the `dtrack` profile so the default stack does not pull in
# Dependency-Track (issue #1432: DT consumes ~4 GiB RAM and pegs the
# bundled Postgres while idle).
# Docker Hub alternative: artifactkeeper/backend:${ARTIFACT_KEEPER_VERSION:-latest}
dtrack-init:
image: ghcr.io/artifact-keeper/artifact-keeper-backend:${ARTIFACT_KEEPER_VERSION:-latest}
container_name: artifact-keeper-dtrack-init
profiles:
- dtrack # Only starts with: docker compose --profile dtrack up
depends_on:
postgres:
condition: service_healthy
dependency-track-apiserver:
condition: service_healthy
volumes:
- ./docker/init-dtrack.sh:/init-dtrack.sh:ro,z
- shared_config:/shared
entrypoint: ["/bin/sh", "/init-dtrack.sh"]
restart: "no"
healthcheck:
disable: true
# ---------------------------------------------------------------------------
# Backend API server
# ---------------------------------------------------------------------------
# The core Artifact Keeper service. Handles all API requests, package format
# protocols, storage, and integrations with scanners.
# Docker Hub alternative: artifactkeeper/backend:${ARTIFACT_KEEPER_VERSION:-latest}
#
# Key environment variables to review for your deployment:
# JWT_SECRET - MUST be changed for production (use a random 32+ char string)
# ADMIN_PASSWORD - if unset, a random password is generated on first boot
# STORAGE_PATH - where artifacts are stored on disk (or configure S3 below)
# CORS_ORIGINS - set to your actual domain(s) in production
#
# For S3-compatible object storage, set S3_BUCKET, S3_REGION, S3_ACCESS_KEY,
# S3_SECRET_KEY, and STORAGE_BACKEND=s3. See .env.example for all options.
backend:
image: ghcr.io/artifact-keeper/artifact-keeper-backend:${ARTIFACT_KEEPER_VERSION:-latest}
container_name: artifact-keeper-backend
depends_on:
postgres:
condition: service_healthy
opensearch:
condition: service_healthy
entrypoint:
- /bin/sh
- -c
- |
# Wait for dtrack-init to provision the API key (up to 5 min)
if [ "$${DEPENDENCY_TRACK_ENABLED}" != "false" ]; then
echo "backend: waiting for Dependency-Track API key..."
for i in $$(seq 1 60); do
if [ -f /shared/dtrack-api-key ] && [ -s /shared/dtrack-api-key ]; then
export DEPENDENCY_TRACK_API_KEY="$$(cat /shared/dtrack-api-key)"
echo "backend: Dependency-Track API key loaded"
break
fi
if [ "$$i" -eq 60 ]; then
echo "backend: WARNING - Dependency-Track API key not found after 5 min, starting without it"
fi
sleep 5
done
fi
exec artifact-keeper
environment:
DATABASE_URL: postgresql://registry:registry@postgres:5432/artifact_registry?sslmode=require
STORAGE_PATH: /data/storage
BACKUP_PATH: /data/backups
PLUGINS_DIR: /data/plugins
JWT_SECRET: ${JWT_SECRET:-change-me-in-production-please}
# ADMIN_PASSWORD: set this to skip first-boot setup lock.
# If unset, a random password is generated and written to
# /data/storage/admin.password on each boot until it is changed.
# Read it with: docker exec artifact-keeper-backend cat /data/storage/admin.password
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-}
RUST_LOG: ${RUST_LOG:-info,artifact_keeper=debug}
ENVIRONMENT: ${ENVIRONMENT:-development}
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost,https://localhost}
TRIVY_URL: http://trivy:8090
OPENSCAP_URL: http://openscap:8091
SCAN_WORKSPACE_PATH: /scan-workspace
OPENSEARCH_URL: http://opensearch:9200
# Dependency-Track integration (API key auto-provisioned by dtrack-init)
DEPENDENCY_TRACK_URL: http://dependency-track-apiserver:8080
# Default off: Dependency-Track is an optional integration that runs a
# heavyweight Java service plus its own NVD mirror. Operators opt in by
# setting DEPENDENCY_TRACK_ENABLED=true *and* starting the dtrack
# profile: `docker compose --profile dtrack up`. See issue #1432.
DEPENDENCY_TRACK_ENABLED: ${DEPENDENCY_TRACK_ENABLED:-false}
ALLOW_HTTP_INTEGRATIONS: "1"
# Webhooks v2 signing-secret encryption key. 32 bytes, base64 encoded.
# Generate with: openssl rand -base64 32, then set the variable in
# your shell or a .env file before docker compose up. The placeholder
# below is intentionally invalid base64 so the backend fails fast at
# boot (ensure_configured) with a clear error rather than running
# with a known-weak key. Without a key set, webhook create and
# rotate-secret endpoints return HTTP 500.
AK_WEBHOOK_SECRET_KEY: ${AK_WEBHOOK_SECRET_KEY:-REPLACE_ME_WITH_OUTPUT_OF_openssl_rand_base64_32}
# Toggle the webhooks v2 EventBus producer. Default off. Flip to true
# once you have rotated migrated webhook secrets via /rotate-secret
# and verified delivery against your receivers.
WEBHOOKS_V2_PRODUCER_ENABLED: ${WEBHOOKS_V2_PRODUCER_ENABLED:-false}
# Forward host proxy settings so the backend can reach external services
# (OIDC providers, remote registries, webhook endpoints) through a
# corporate proxy. reqwest honours these natively.
HTTP_PROXY: ${HTTP_PROXY:-}
HTTPS_PROXY: ${HTTPS_PROXY:-}
ALL_PROXY: ${ALL_PROXY:-}
# Prevent host proxy settings (HTTP_PROXY/HTTPS_PROXY) from intercepting
# container-to-container traffic. Docker and Podman propagate proxy vars
# from the host, which causes health checks and API calls to route through
# corporate proxies instead of the compose network. We append internal
# service names so they always resolve directly.
# ref: https://github.com/artifact-keeper/artifact-keeper/issues/395
no_proxy: "${NO_PROXY:-},localhost,127.0.0.1,db,postgres,opensearch,trivy,openscap,dependency-track-apiserver"
HOST: 0.0.0.0
PORT: 8080
volumes:
- artifact_storage:/data/storage
- backup_storage:/data/backups
- plugins_storage:/data/plugins
- scan_workspace:/scan-workspace
- shared_config:/shared:ro
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/livez"]
interval: 30s
timeout: 10s
start_period: 10s
retries: 3
restart: unless-stopped
# ---------------------------------------------------------------------------
# OpenSearch (full-text search engine)
# ---------------------------------------------------------------------------
# Powers artifact and package search in the UI and API.
opensearch:
image: opensearchproject/opensearch:2.19.1
container_name: artifact-keeper-opensearch
environment:
discovery.type: single-node
OPENSEARCH_JAVA_OPTS: "-Xms512m -Xmx512m"
DISABLE_SECURITY_PLUGIN: "true"
DISABLE_INSTALL_DEMO_CONFIG: "true"
volumes:
- opensearch_data:/usr/share/opensearch/data
ports:
- "9200:9200"
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:9200/_cluster/health || exit 1"]
interval: 10s
timeout: 10s
retries: 12
start_period: 30s
restart: unless-stopped
# ---------------------------------------------------------------------------
# Trivy (vulnerability scanner)
# ---------------------------------------------------------------------------
# Scans uploaded artifacts for known vulnerabilities. Runs as a server that
# the backend calls on demand. Optional: remove this service and unset
# TRIVY_URL on the backend if you do not need vulnerability scanning.
trivy:
image: ghcr.io/aquasecurity/trivy:0.69.3
container_name: artifact-keeper-trivy
command: ["server", "--listen", "0.0.0.0:8090"]
ports:
- "8090:8090"
volumes:
- trivy_cache:/root/.cache/trivy
- scan_workspace:/scan-workspace:ro
restart: unless-stopped
# ---------------------------------------------------------------------------
# OpenSCAP (compliance scanner)
# ---------------------------------------------------------------------------
# Runs SCAP compliance checks against container images. Optional: remove
# this service and unset OPENSCAP_URL on the backend if not needed.
openscap:
image: ghcr.io/artifact-keeper/artifact-keeper-openscap:latest # or artifactkeeper/openscap:latest
container_name: artifact-keeper-openscap
ports:
- "8091:8091"
volumes:
- scan_workspace:/scan-workspace:ro
healthcheck:
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8091/health')"]
interval: 30s
timeout: 5s
retries: 3
restart: unless-stopped
# ---------------------------------------------------------------------------
# Dependency-Track API server (SBOM analysis)
# ---------------------------------------------------------------------------
# OWASP Dependency-Track provides SBOM ingestion, license analysis, and
# vulnerability tracking. Gated behind the `dtrack` profile because the
# Java apiserver consumes ~4 GiB RAM and continuously drives the bundled
# Postgres even with no inbound load (issue #1432). Opt in with:
# docker compose --profile dtrack up
# and set DEPENDENCY_TRACK_ENABLED=true on the backend so the integration
# actually fires. To also run the upstream DT UI alongside, combine the
# profiles:
# docker compose --profile dtrack --profile dtrack-ui up
# https://dependencytrack.org/
dependency-track-apiserver:
image: dependencytrack/apiserver:4.14.2
container_name: artifact-keeper-dtrack-api
profiles:
- dtrack # Only starts with: docker compose --profile dtrack up
depends_on:
postgres:
condition: service_healthy
environment:
# Database configuration (uses same Postgres, different database)
ALPINE_DATABASE_MODE: external
ALPINE_DATABASE_URL: jdbc:postgresql://postgres:5432/dependency_track
ALPINE_DATABASE_DRIVER: org.postgresql.Driver
ALPINE_DATABASE_USERNAME: registry
ALPINE_DATABASE_PASSWORD: registry
# API configuration
ALPINE_DATA_DIRECTORY: /data
# Enable API key authentication
ALPINE_ENFORCE_AUTHENTICATION: "true"
# Optional: Enable CORS for frontend
ALPINE_CORS_ENABLED: "true"
ALPINE_CORS_ALLOW_ORIGIN: "*"
# Proxy configuration (required for NVD mirroring behind corporate proxies)
ALPINE_HTTP_PROXY_ADDRESS: ${DTRACK_HTTP_PROXY_ADDRESS:-}
ALPINE_HTTP_PROXY_PORT: ${DTRACK_HTTP_PROXY_PORT:-}
ALPINE_HTTP_PROXY_USERNAME: ${DTRACK_HTTP_PROXY_USERNAME:-}
ALPINE_HTTP_PROXY_PASSWORD: ${DTRACK_HTTP_PROXY_PASSWORD:-}
ALPINE_NO_PROXY: ${DTRACK_NO_PROXY:-localhost,127.0.0.1}
volumes:
- dtrack_data:/data
ports:
- "8092:8080"
restart: unless-stopped
# Dependency-Track native frontend (optional, for debugging/admin).
# Artifact Keeper integrates DT data into its own UI, so this is only
# useful for direct Dependency-Track access. Gated behind the `dtrack-ui`
# profile, which implies `dtrack` (the upstream UI is useless without the
# apiserver). Run with:
# docker compose --profile dtrack --profile dtrack-ui up
dependency-track-frontend:
image: dependencytrack/frontend:4.14.2
container_name: artifact-keeper-dtrack-frontend
profiles:
- dtrack-ui # Requires the `dtrack` profile too; see comment above.
depends_on:
dependency-track-apiserver:
condition: service_healthy
environment:
API_BASE_URL: http://localhost:8092
ports:
- "8093:8080"
restart: unless-stopped
# ---------------------------------------------------------------------------
# Web UI (Next.js)
# ---------------------------------------------------------------------------
# The Artifact Keeper web frontend. Communicates with the backend over the
# internal Docker network. No ports are exposed directly; Caddy handles
# external access and routes requests to the appropriate service.
web:
image: ghcr.io/artifact-keeper/artifact-keeper-web:latest # or artifactkeeper/web:latest
container_name: artifact-keeper-web
depends_on:
backend:
condition: service_started
environment:
BACKEND_URL: http://backend:8080
restart: unless-stopped
# ---------------------------------------------------------------------------
# Caddy (reverse proxy / TLS termination)
# ---------------------------------------------------------------------------
# Routes external traffic to the backend API and web UI. Automatically
# provisions TLS certificates via ACME when SITE_ADDRESS is set to a public
# domain. For local evaluation it defaults to localhost with a self-signed
# cert. In production, you may replace Caddy with your own reverse proxy
# (nginx, Traefik, a cloud load balancer, etc.).
caddy:
image: caddy:2-alpine
container_name: artifact-keeper-caddy
depends_on:
backend:
condition: service_started
web:
condition: service_started
environment:
SITE_ADDRESS: ${SITE_ADDRESS:-localhost}
# Clear any proxy env vars inherited from the Docker daemon config.
# When the daemon is configured with an HTTP proxy (common in
# enterprise networks), those vars are injected into all containers.
# Caddy (a Go app) honours them and would try to route reverse-proxy
# traffic to sibling containers through the external proxy, which
# breaks inter-container communication.
# See: https://docs.docker.com/engine/daemon/proxy/
HTTP_PROXY: ""
HTTPS_PROXY: ""
http_proxy: ""
https_proxy: ""
NO_PROXY: "localhost,127.0.0.1"
no_proxy: "localhost,127.0.0.1"
ports:
- "${HTTP_PORT:-30080}:80"
- "${HTTPS_PORT:-30443}:443"
volumes:
- ./docker/Caddyfile:/etc/caddy/Caddyfile:ro,z
- caddy_data:/data
- caddy_config:/config
restart: unless-stopped
# =============================================================================
# Volumes
# =============================================================================
# All volumes use the local driver for simplicity. In production, consider
# backing critical data (postgres_data, artifact_storage) with durable storage
# or external services (managed databases, S3, etc.).
volumes:
postgres_data:
driver: local
artifact_storage:
driver: local
backup_storage:
driver: local
plugins_storage:
driver: local
trivy_cache:
driver: local
opensearch_data:
driver: local
scan_workspace:
driver: local
caddy_data:
driver: local
caddy_config:
driver: local
dtrack_data:
driver: local
shared_config:
driver: local
pg_certs:
driver: local
# =============================================================================
# Network
# =============================================================================
# A single bridge network for all services. All inter-container communication
# stays on this network; only Caddy (and optionally Postgres/OpenSearch for
# debugging) expose ports to the host.
networks:
default:
name: artifact-keeper-network