diff options
| author | agent <agent@local> | 2026-11-07 09:00:55 +0100 |
|---|---|---|
| committer | agent <agent@local> | 2026-11-07 09:00:55 +0100 |
| commit | 54541fa8357cf2af616effbaeb7d5b8d76f80ea2 (patch) | |
| tree | 0be5059280b10545912e01081f7d95aaf9055623 /deploy | |
Public read-only distroless OCI mirror (zot + edge vhost + auditor)
Diffstat (limited to 'deploy')
| -rw-r--r-- | deploy/config.json | 88 | ||||
| -rw-r--r-- | deploy/docker-compose.yml | 28 | ||||
| -rw-r--r-- | deploy/nginx-edge-img.conf | 68 |
3 files changed, 184 insertions, 0 deletions
diff --git a/deploy/config.json b/deploy/config.json new file mode 100644 index 0000000..642046a --- /dev/null +++ b/deploy/config.json @@ -0,0 +1,88 @@ +{ + "distSpecVersion": "1.1.1", + "storage": { + "rootDirectory": "/var/lib/registry", + "gc": true, + "gcDelay": "1h", + "dedupe": true + }, + "http": { + "address": "0.0.0.0", + "port": "5000", + "realm": "distroless-mirror", + "accessControl": { + "repositories": { + "**": { + "anonymousPolicy": [ + "read" + ] + } + } + } + }, + "log": { + "level": "info", + "output": "/dev/stdout" + }, + "extensions": { + "sync": { + "enable": true, + "downloadDir": "/var/lib/registry/.sync", + "registries": [ + { + "urls": [ + "https://gcr.io" + ], + "tlsVerify": true, + "onDemand": true, + "manifestCheckInterval": "30m", + "maxRetries": 3, + "retryDelay": "5m", + "maxRetryDelay": "30m", + "content": [ + { + "prefix": "distroless/static", + "tags": { + "regex": "^(latest|nonroot|debug|debug-nonroot)$" + }, + "platforms": [ + "linux/amd64", + "linux/arm64" + ] + }, + { + "prefix": "distroless/base", + "tags": { + "regex": "^(latest|nonroot|debug|debug-nonroot)$" + }, + "platforms": [ + "linux/amd64", + "linux/arm64" + ] + }, + { + "prefix": "distroless/cc", + "tags": { + "regex": "^(latest|nonroot|debug|debug-nonroot)$" + }, + "platforms": [ + "linux/amd64", + "linux/arm64" + ] + }, + { + "prefix": "distroless/python3", + "tags": { + "regex": "^(latest|nonroot|debug|debug-nonroot)$" + }, + "platforms": [ + "linux/amd64", + "linux/arm64" + ] + } + ] + } + ] + } + } +} diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml new file mode 100644 index 0000000..e486a2f --- /dev/null +++ b/deploy/docker-compose.yml @@ -0,0 +1,28 @@ +# Public read-only OCI mirror of the four upstream Google distroless images. +# +# Serving path: public :443 -> edge-nginx (host network, TLS + CrowdSec) -> +# 127.0.0.1:5000 -> this container. The port publish is loopback-only on +# purpose: nothing on any host interface can reach zot directly, so the edge +# vhost is the only entry point. +# +# There is no `auth` block and anonymous users only get `read`, so the registry +# has no push surface at all - not from the internet and not from the LAN. +# Content arrives exclusively through the built-in sync extension pulling from +# gcr.io, which is also why the mirror keeps working while the home Forgejo +# instance (and its runner) is down. +services: + zot: + image: ghcr.io/project-zot/zot:v2.1.23 + container_name: zot-mirror + command: ["serve", "/etc/zot/config.json"] + restart: unless-stopped + ports: + - "127.0.0.1:5000:5000" + volumes: + - ./data:/var/lib/registry + - ./config.json:/etc/zot/config.json:ro + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" diff --git a/deploy/nginx-edge-img.conf b/deploy/nginx-edge-img.conf new file mode 100644 index 0000000..3d6ac16 --- /dev/null +++ b/deploy/nginx-edge-img.conf @@ -0,0 +1,68 @@ +# --- img.dieschoenewolke.de: public read-only OCI mirror --------------------- +# Merge this block into /root/Compose/Webserver/NginxEdge/nginx/default.conf +# (write in place: `cat new > default.conf`) and force-recreate edge-nginx. +# +# docker/containerd/skopeo/crane all speak plain GET+HEAD with a Go or Docker +# user agent. CRS treats those as scanners, so AppSec is off for this host - the +# CrowdSec IP bouncer still applies and the registry itself is read-only. +# `limit_except GET` is the second lock: even if zot were reconfigured, the edge +# refuses every write verb. +# +# A path-based /v2/ on git.dieschoenewolke.de is NOT an option: OCI clients +# build the repository name from <host>/<path>, so a shared host would force +# names like git.dieschoenewolke.de/distroless/static and the /v2/ base path +# would have to be stripped by the edge. docker's ping is +# GET /v2/ (not /git/v2/), so the mirror gets its own name. The wildcard +# *.dieschoenewolke.de certificate already covers it - no cert change. +server { + listen 443 ssl; + listen [::]:443 ssl; + listen 443 quic; + listen [::]:443 quic; + http2 on; + server_name img.dieschoenewolke.de; + access_log /dev/stdout edge; + + set $crowdsec_disable_appsec 1; + + add_header Alt-Svc 'h3=":443"; ma=86400' always; + + ssl_certificate /certs/edge.crt; + ssl_certificate_key /certs/edge.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_ecdh_curve X25519:prime256v1:secp384r1; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + ssl_session_tickets off; + + more_set_headers + "Strict-Transport-Security: max-age=31536000; includeSubDomains" + "X-Content-Type-Options: nosniff" + "Referrer-Policy: strict-origin-when-cross-origin"; + + # a manifest/index is a few hundred KB; layers are streamed, never buffered + client_max_body_size 4m; + + location / { + limit_except GET { + deny all; + } + + proxy_pass http://127.0.0.1:5000; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + # stream blobs instead of spooling them to the edge disk (28 GB root) + proxy_buffering off; + proxy_max_temp_file_size 0; + proxy_request_buffering off; + proxy_read_timeout 900s; + proxy_send_timeout 900s; + proxy_cache off; + } +} |
