Files
ysj7705andClaude Opus 5.5 89c6cbb5ad Fix image deletion: per-tag delete, repository delete, daily GC.
- 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>
2026-10-05 12:31:16 +09:00

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: