- Deleting a tag now untags only that tag; the manifest is removed only when no other tag references the same digest (previously deleting by digest also removed every sibling tag). - Add repository deletion. The registry API cannot drop a repository from the catalog, so empty repositories stayed in the UI forever with nothing left to delete. The app now mounts registry storage and removes the repository's metadata directories after deleting its manifests. - Run `registry garbage-collect` daily (GC_HOUR_UTC) so deleted blobs free disk. - Disable Watchtower for the locally built app image. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
129 lines
4.6 KiB
YAML
129 lines
4.6 KiB
YAML
services:
|
|
certs:
|
|
image: alpine:3.21
|
|
restart: "no"
|
|
volumes:
|
|
- ./certs:/certs
|
|
entrypoint: ["/bin/sh", "-c"]
|
|
command:
|
|
- |
|
|
set -eu
|
|
apk add --no-cache openssl >/dev/null
|
|
if [ -f /certs/token.key ] && [ -f /certs/token.crt ]; then
|
|
echo "Token certs already exist, skipping."
|
|
exit 0
|
|
fi
|
|
openssl req -new -newkey rsa:4096 -days 3650 -nodes -x509 \
|
|
-subj "/CN=registry-aio-token" \
|
|
-keyout /certs/token.key \
|
|
-out /certs/token.crt
|
|
chmod 600 /certs/token.key
|
|
echo "Generated /certs/token.key and /certs/token.crt"
|
|
|
|
registry:
|
|
image: registry:3
|
|
restart: unless-stopped
|
|
depends_on:
|
|
certs:
|
|
condition: service_completed_successfully
|
|
ports:
|
|
- "${REGISTRY_PORT:-5050}:5000"
|
|
environment:
|
|
AUTH_PUBLIC_URL: ${AUTH_PUBLIC_URL:-http://localhost:3080}
|
|
TOKEN_SERVICE: ${REGISTRY_AUTH_SERVICE:-registry}
|
|
TOKEN_ISSUER: ${REGISTRY_AUTH_ISSUER:-registry-aio}
|
|
# Daily blob garbage collection hour (UTC, 00-23). Empty disables it.
|
|
GC_HOUR_UTC: ${GC_HOUR_UTC-20}
|
|
volumes:
|
|
- registry-data:/var/lib/registry
|
|
- ./certs:/certs:ro
|
|
- ./config/registry/config.yml.template:/config/config.yml.template:ro
|
|
entrypoint: ["/bin/sh", "-c"]
|
|
command:
|
|
- |
|
|
set -eu
|
|
: "$${AUTH_PUBLIC_URL:?AUTH_PUBLIC_URL is required}"
|
|
TOKEN_SERVICE="$${TOKEN_SERVICE:-registry}"
|
|
TOKEN_ISSUER="$${TOKEN_ISSUER:-registry-aio}"
|
|
mkdir -p /etc/docker/registry
|
|
sed \
|
|
-e "s|__AUTH_PUBLIC_URL__|$${AUTH_PUBLIC_URL}|g" \
|
|
-e "s|__REGISTRY_AUTH_SERVICE__|$${TOKEN_SERVICE}|g" \
|
|
-e "s|__REGISTRY_AUTH_ISSUER__|$${TOKEN_ISSUER}|g" \
|
|
/config/config.yml.template > /etc/docker/registry/config.yml
|
|
echo "Rendered registry config:"
|
|
cat /etc/docker/registry/config.yml
|
|
# Deleting tags/manifests only drops references; blobs stay on disk until GC runs.
|
|
if [ -n "$${GC_HOUR_UTC:-}" ]; then
|
|
(
|
|
last=""
|
|
while sleep 600; do
|
|
today="$$(date -u +%F)"
|
|
if [ "$$(date -u +%H)" = "$${GC_HOUR_UTC}" ] && [ "$$today" != "$$last" ]; then
|
|
last="$$today"
|
|
echo "garbage-collect: start"
|
|
registry garbage-collect /etc/docker/registry/config.yml 2>&1 | tail -n 5
|
|
echo "garbage-collect: done"
|
|
fi
|
|
done
|
|
) &
|
|
fi
|
|
exec registry serve /etc/docker/registry/config.yml
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:5000/v2/ || exit 0"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 5s
|
|
|
|
app:
|
|
build:
|
|
context: ./app
|
|
dockerfile: Dockerfile
|
|
labels:
|
|
- com.centurylinklabs.watchtower.enable=false
|
|
restart: unless-stopped
|
|
depends_on:
|
|
certs:
|
|
condition: service_completed_successfully
|
|
ports:
|
|
- "${APP_PORT:-3080}:3000"
|
|
environment:
|
|
NODE_ENV: production
|
|
HOST: 0.0.0.0
|
|
PORT: 3000
|
|
DATABASE_URL: file:/data/registry-aio.db
|
|
AUTH_PUBLIC_URL: ${AUTH_PUBLIC_URL:-http://localhost:3080}
|
|
REGISTRY_PUBLIC_URL: ${REGISTRY_PUBLIC_URL:-localhost:5050}
|
|
REGISTRY_AUTH_SERVICE: ${REGISTRY_AUTH_SERVICE:-registry}
|
|
REGISTRY_AUTH_ISSUER: ${REGISTRY_AUTH_ISSUER:-registry-aio}
|
|
REGISTRY_INTERNAL_URL: ${REGISTRY_INTERNAL_URL:-http://registry:5000}
|
|
REGISTRY_PORT: ${REGISTRY_PORT:-5050}
|
|
# runtimeConfig 기본값은 빌드 시 고정되므로, 런타임 반영은 NUXT_ 접두사가 필요
|
|
NUXT_PUBLIC_AUTH_PUBLIC_URL: ${AUTH_PUBLIC_URL:-http://localhost:3080}
|
|
NUXT_PUBLIC_REGISTRY_PUBLIC_URL: ${REGISTRY_PUBLIC_URL:-localhost:5050}
|
|
NUXT_PUBLIC_REGISTRY_PORT: ${REGISTRY_PORT:-5050}
|
|
NUXT_REGISTRY_INTERNAL_URL: ${REGISTRY_INTERNAL_URL:-http://registry:5000}
|
|
NUXT_SESSION_SECRET: ${SESSION_SECRET:-change-me-to-a-long-random-string}
|
|
ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
|
|
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-admin123}
|
|
SESSION_SECRET: ${SESSION_SECRET:-change-me-to-a-long-random-string}
|
|
TOKEN_KEY_PATH: /certs/token.key
|
|
TOKEN_CERT_PATH: /certs/token.crt
|
|
# Shared registry storage: lets the UI remove empty repositories from the catalog
|
|
REGISTRY_STORAGE_PATH: /var/lib/registry
|
|
volumes:
|
|
- app-data:/data
|
|
- ./certs:/certs:ro
|
|
- registry-data:/var/lib/registry
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/api/health"]
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 10
|
|
start_period: 20s
|
|
|
|
volumes:
|
|
registry-data:
|
|
app-data:
|