mirror of
https://github.com/ArchiveBox/ArchiveBox.git
synced 2026-09-14 11:06:13 +05:00
feat(docker): add opt-in bundled reverse-proxy profile (Caddy + lego)
Make the default safe-subdomains-fullreplay mode painless to deploy
without baking heavy proxy/cert deps into the archivebox image.
docker-compose.proxy.yml is an opt-in overlay that adds:
- caddy: wildcard catch-all reverse proxy (etc/Caddyfile) that routes
every dynamically-generated snapshot/role subdomain to archivebox by
suffix wildcard (never an enumerated list), preserves Host, and sets
X-Forwarded-Proto. Local default serves *.archivebox.localhost via
Caddy's internal CA.
- lego (proxy-public profile): a go-acme/lego DNS-01 sidecar
(bin/proxy_lego.sh) that fetches ONE *.<domain> wildcard cert covering
unlimited snap-* subdomains. Provider coverage (~150: cloudflare,
route53, gcloud, digitalocean, hetzner, namecheap, godaddy, ...) is
delegated to lego via standard env vars, with no per-provider code.
When proxied, the archivebox service stops publishing :8000 directly and
sets REVERSE_PROXY_TRUST_FORWARDED_PROTO=True so secure cookies + https
links work end-to-end. Replaces the stale nginx example in
docker-compose.yml. See .env.proxy.example for public setup.
This commit is contained in:
parent
a9cc7f887b
commit
a7514a7eb8
42
.env.proxy.example
Normal file
42
.env.proxy.example
Normal file
@ -0,0 +1,42 @@
|
||||
# Example env for the bundled reverse-proxy profile (docker-compose.proxy.yml).
|
||||
# Copy to `.env` and edit for a PUBLIC deployment with automatic wildcard TLS.
|
||||
# For LOCAL *.archivebox.localhost you don't need any of this.
|
||||
|
||||
# ---- Your public base domain (ArchiveBox lives at admin./web./api./public./snap-*.<this>) ----
|
||||
ARCHIVEBOX_PROXY_DOMAIN=archive.example.com
|
||||
|
||||
# Tell Caddy to serve the single *.<domain> wildcard cert the lego sidecar fetches:
|
||||
ARCHIVEBOX_PROXY_SITE=*.archive.example.com archive.example.com
|
||||
ARCHIVEBOX_PROXY_TLS=/certs/wildcard.crt /certs/wildcard.key
|
||||
|
||||
# ---- DNS-01 issuance (no per-provider hand-coding; lego does ~150 providers) ----
|
||||
ARCHIVEBOX_PROXY_DNS_PROVIDER=cloudflare
|
||||
ARCHIVEBOX_PROXY_ACME_EMAIL=you@example.com
|
||||
# Set =1 while testing to use Let's Encrypt staging (avoids prod rate limits):
|
||||
ARCHIVEBOX_PROXY_ACME_STAGING=0
|
||||
|
||||
# ---- Provider credentials: pass through the vars YOUR provider needs ----
|
||||
# (names come straight from https://go-acme.github.io/lego/dns/)
|
||||
# cloudflare:
|
||||
CLOUDFLARE_DNS_API_TOKEN=your-scoped-token
|
||||
# route53 (AWS):
|
||||
# AWS_ACCESS_KEY_ID=...
|
||||
# AWS_SECRET_ACCESS_KEY=...
|
||||
# AWS_REGION=us-east-1
|
||||
# digitalocean:
|
||||
# DO_AUTH_TOKEN=...
|
||||
# hetzner:
|
||||
# HETZNER_API_KEY=...
|
||||
# gcloud:
|
||||
# GCE_PROJECT=...
|
||||
# GOOGLE_APPLICATION_CREDENTIALS=/path/in/container.json
|
||||
# namecheap:
|
||||
# NAMECHEAP_API_USER=...
|
||||
# NAMECHEAP_API_KEY=...
|
||||
# godaddy:
|
||||
# GODADDY_API_KEY=...
|
||||
# GODADDY_API_SECRET=...
|
||||
|
||||
# Don't forget the one-time wildcard DNS A/AAAA record at your provider:
|
||||
# *.archive.example.com -> <your server's public IP>
|
||||
# archive.example.com -> <your server's public IP>
|
||||
65
bin/proxy_lego.sh
Executable file
65
bin/proxy_lego.sh
Executable file
@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env sh
|
||||
# DNS-01 wildcard cert sidecar for the bundled ArchiveBox reverse-proxy profile.
|
||||
#
|
||||
# ArchiveBox serves unlimited dynamically-generated snapshot subdomains
|
||||
# (snap-<id>.<base>), so per-host cert issuance can't scale — we obtain ONE
|
||||
# `*.<base>` wildcard cert via DNS-01 and let Caddy serve it for every subdomain.
|
||||
#
|
||||
# Provider coverage is delegated entirely to go-acme/lego, which speaks DNS-01
|
||||
# to ~150 providers (cloudflare, route53/AWS, gcloud, digitalocean, hetzner,
|
||||
# namecheap, godaddy, ...). We do NOT hand-code providers here: you pick one
|
||||
# with ARCHIVEBOX_PROXY_DNS_PROVIDER and pass its credentials through as the
|
||||
# standard env vars lego documents for that provider, e.g.
|
||||
# cloudflare -> CLOUDFLARE_DNS_API_TOKEN
|
||||
# route53 -> AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION
|
||||
# gcloud -> GCE_PROJECT / GOOGLE_APPLICATION_CREDENTIALS
|
||||
# digitalocean-> DO_AUTH_TOKEN
|
||||
# hetzner -> HETZNER_API_KEY
|
||||
# namecheap -> NAMECHEAP_API_USER / NAMECHEAP_API_KEY
|
||||
# godaddy -> GODADDY_API_KEY / GODADDY_API_SECRET
|
||||
# Full list + var names: https://go-acme.github.io/lego/dns/
|
||||
set -eu
|
||||
|
||||
DOMAIN="${ARCHIVEBOX_PROXY_DOMAIN:?set ARCHIVEBOX_PROXY_DOMAIN to your base domain, e.g. archive.example.com}"
|
||||
PROVIDER="${ARCHIVEBOX_PROXY_DNS_PROVIDER:?set ARCHIVEBOX_PROXY_DNS_PROVIDER to a lego DNS provider, e.g. cloudflare}"
|
||||
EMAIL="${ARCHIVEBOX_PROXY_ACME_EMAIL:?set ARCHIVEBOX_PROXY_ACME_EMAIL to a contact email for ACME}"
|
||||
CERT_DIR="${ARCHIVEBOX_PROXY_CERT_DIR:-/certs}"
|
||||
LEGO_PATH="${CERT_DIR}/lego"
|
||||
RENEW_INTERVAL="${ARCHIVEBOX_PROXY_RENEW_INTERVAL:-43200}" # 12h
|
||||
# Staging CA for testing avoids burning prod rate limits; set =1 to use it.
|
||||
CA_FLAG=""
|
||||
if [ "${ARCHIVEBOX_PROXY_ACME_STAGING:-0}" = "1" ]; then
|
||||
CA_FLAG="--server=https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
fi
|
||||
|
||||
mkdir -p "${LEGO_PATH}"
|
||||
|
||||
# lego writes wildcard certs as `_.<domain>.crt` / `.key`; publish them under
|
||||
# the stable names the Caddyfile points at (ARCHIVEBOX_PROXY_TLS).
|
||||
publish() {
|
||||
src="${LEGO_PATH}/certificates/_.${DOMAIN}"
|
||||
if [ -f "${src}.crt" ] && [ -f "${src}.key" ]; then
|
||||
cp -f "${src}.crt" "${CERT_DIR}/wildcard.crt"
|
||||
cp -f "${src}.key" "${CERT_DIR}/wildcard.key"
|
||||
echo "[proxy_lego] published *.${DOMAIN} wildcard cert -> ${CERT_DIR}/wildcard.{crt,key}"
|
||||
else
|
||||
echo "[proxy_lego] expected cert at ${src}.{crt,key} not found" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# One `run` to issue, then `renew` on each later pass (lego no-ops if not due).
|
||||
ACTION="run"
|
||||
while true; do
|
||||
echo "[proxy_lego] ${ACTION} *.${DOMAIN} via ${PROVIDER} (DNS-01)"
|
||||
if lego --accept-tos --email "${EMAIL}" --dns "${PROVIDER}" \
|
||||
--domains "*.${DOMAIN}" --domains "${DOMAIN}" \
|
||||
--path "${LEGO_PATH}" ${CA_FLAG} "${ACTION}" ${RENEW_FLAGS:-}; then
|
||||
publish || true
|
||||
else
|
||||
echo "[proxy_lego] lego ${ACTION} failed; will retry next interval" >&2
|
||||
fi
|
||||
ACTION="renew"
|
||||
RENEW_FLAGS="--days 30"
|
||||
sleep "${RENEW_INTERVAL}"
|
||||
done
|
||||
78
docker-compose.proxy.yml
Normal file
78
docker-compose.proxy.yml
Normal file
@ -0,0 +1,78 @@
|
||||
# Opt-in bundled reverse-proxy overlay for ArchiveBox.
|
||||
#
|
||||
# This gives the "Plex-easy" path: ArchiveBox stays on the default
|
||||
# safe-subdomains-fullreplay mode (full JS replay + per-snapshot origin
|
||||
# isolation), and a bundled Caddy terminates TLS and wildcard-routes every
|
||||
# dynamically-generated subdomain to it. The heavy proxy/cert deps live HERE,
|
||||
# never in the core archivebox image.
|
||||
#
|
||||
# Usage (LOCAL, *.archivebox.localhost via Caddy's internal CA):
|
||||
# docker compose -f docker-compose.yml -f docker-compose.proxy.yml up -d
|
||||
# # then `caddy trust` / install Caddy's root CA to silence the browser warning,
|
||||
# # or just use http://...:8000 directly for plain-HTTP local browsing.
|
||||
#
|
||||
# Usage (PUBLIC, real domain + automatic *.<domain> wildcard cert via DNS-01):
|
||||
# cp .env.proxy.example .env # set ARCHIVEBOX_PROXY_DOMAIN + provider creds
|
||||
# docker compose -f docker-compose.yml -f docker-compose.proxy.yml up -d
|
||||
#
|
||||
# See etc/Caddyfile and bin/proxy_lego.sh for the wildcard/cert mechanics.
|
||||
|
||||
services:
|
||||
archivebox:
|
||||
# When proxied, only Caddy is published to the host. Drop the direct
|
||||
# 8000 publish so X-Forwarded-Proto can't be spoofed by a client that
|
||||
# bypasses the proxy, and tell ArchiveBox to trust Caddy's forwarded
|
||||
# scheme (so secure cookies + https links work end-to-end).
|
||||
ports: !reset []
|
||||
expose:
|
||||
- "8000"
|
||||
environment:
|
||||
- REVERSE_PROXY_TRUST_FORWARDED_PROTO=True
|
||||
# In public mode, point links/redirects at your real https URL:
|
||||
# - BASE_URL=https://${ARCHIVEBOX_PROXY_DOMAIN:-archive.example.com}
|
||||
|
||||
caddy:
|
||||
image: caddy:2-alpine
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- archivebox
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp" # HTTP/3
|
||||
environment:
|
||||
# Local default below; for public set these (see .env.proxy.example):
|
||||
# ARCHIVEBOX_PROXY_SITE="*.${ARCHIVEBOX_PROXY_DOMAIN} ${ARCHIVEBOX_PROXY_DOMAIN}"
|
||||
# ARCHIVEBOX_PROXY_TLS="/certs/wildcard.crt /certs/wildcard.key"
|
||||
- ARCHIVEBOX_PROXY_SITE=${ARCHIVEBOX_PROXY_SITE:-*.archivebox.localhost archivebox.localhost}
|
||||
- ARCHIVEBOX_PROXY_TLS=${ARCHIVEBOX_PROXY_TLS:-internal}
|
||||
- ARCHIVEBOX_PROXY_ACME_EMAIL=${ARCHIVEBOX_PROXY_ACME_EMAIL:-archivebox@localhost}
|
||||
volumes:
|
||||
- ./etc/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- ./data/proxy/caddy:/data
|
||||
- ./data/proxy/certs:/certs:ro
|
||||
command: ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
|
||||
|
||||
# DNS-01 wildcard cert sidecar (PUBLIC mode only). Disabled by default via
|
||||
# the `proxy-public` profile so local users don't need DNS provider creds.
|
||||
lego:
|
||||
image: goacme/lego:latest
|
||||
profiles: ["proxy-public"]
|
||||
restart: unless-stopped
|
||||
entrypoint: ["/bin/sh", "/proxy_lego.sh"]
|
||||
environment:
|
||||
# proxy_lego.sh enforces that these are set (it exits with a clear
|
||||
# error if any are empty); kept as soft defaults here so the local
|
||||
# profile still renders without public-mode vars.
|
||||
- ARCHIVEBOX_PROXY_DOMAIN=${ARCHIVEBOX_PROXY_DOMAIN:-}
|
||||
- ARCHIVEBOX_PROXY_DNS_PROVIDER=${ARCHIVEBOX_PROXY_DNS_PROVIDER:-}
|
||||
- ARCHIVEBOX_PROXY_ACME_EMAIL=${ARCHIVEBOX_PROXY_ACME_EMAIL:-}
|
||||
- ARCHIVEBOX_PROXY_ACME_STAGING=${ARCHIVEBOX_PROXY_ACME_STAGING:-0}
|
||||
# Plus your provider's credential env vars, passed straight through
|
||||
# to lego (e.g. CLOUDFLARE_DNS_API_TOKEN). See bin/proxy_lego.sh.
|
||||
env_file:
|
||||
- path: .env
|
||||
required: false
|
||||
volumes:
|
||||
- ./bin/proxy_lego.sh:/proxy_lego.sh:ro
|
||||
- ./data/proxy/certs:/certs
|
||||
@ -72,18 +72,23 @@ services:
|
||||
# - 127.0.0.1:8080:8080
|
||||
|
||||
|
||||
### Example: Put Nginx in front of the ArchiveBox server for SSL termination and static file serving.
|
||||
# You can also any other ingress provider for SSL like Apache, Caddy, Traefik, Cloudflare Tunnels, etc.
|
||||
# Note you must set up wildcard DNS and TLS *.your.archivebox.domain because snapshots are served from unique subdomains for security.
|
||||
|
||||
# nginx:
|
||||
# image: nginx:alpine
|
||||
# ports:
|
||||
# - 443:443
|
||||
# - 80:80
|
||||
# volumes:
|
||||
# - ./etc/nginx.conf:/etc/nginx/nginx.conf
|
||||
# - ./data:/var/www
|
||||
### TLS / HTTPS: ArchiveBox serves the admin/web/api/public control plane and every
|
||||
# archived snapshot on its own subdomain for security isolation, so a public deployment
|
||||
# needs wildcard DNS + a wildcard TLS cert for *.your.archivebox.domain.
|
||||
#
|
||||
# The bundled, opt-in proxy overlay sets this up for you (Caddy + a go-acme/lego
|
||||
# DNS-01 sidecar that fetches one *.<domain> wildcard cert covering unlimited
|
||||
# snap-* subdomains). The heavy proxy/cert deps stay out of the archivebox image:
|
||||
#
|
||||
# # local *.archivebox.localhost (Caddy internal CA):
|
||||
# docker compose -f docker-compose.yml -f docker-compose.proxy.yml up -d
|
||||
#
|
||||
# # public, automatic wildcard TLS (edit .env from .env.proxy.example first):
|
||||
# docker compose -f docker-compose.yml -f docker-compose.proxy.yml --profile proxy-public up -d
|
||||
#
|
||||
# Already run your own caddy/traefik/nginx or a Cloudflare/Tailscale ingress? Just set
|
||||
# BASE_URL to your https URL and REVERSE_PROXY_TRUST_FORWARDED_PROTO=True, point the
|
||||
# wildcard upstream at this container, and skip the overlay.
|
||||
|
||||
### Example: run all your ArchiveBox traffic through a WireGuard VPN tunnel to avoid IP blocks.
|
||||
# You can also use any other VPN that works at the docker/IP level, e.g. Tailscale, OpenVPN, etc.
|
||||
|
||||
46
etc/Caddyfile
Normal file
46
etc/Caddyfile
Normal file
@ -0,0 +1,46 @@
|
||||
# ArchiveBox bundled reverse-proxy config (opt-in, used by docker-compose.proxy.yml).
|
||||
#
|
||||
# Why a wildcard catch-all and not a list of hosts?
|
||||
# ArchiveBox serves the admin/web/api/public control plane AND every archived
|
||||
# snapshot on its OWN hostname for security isolation:
|
||||
# admin.<base> web.<base> api.<base> public.<base> (control plane)
|
||||
# snap-<12hex>.<base> (snapshot replay)
|
||||
# <original-domain>.<base> (original-domain replay)
|
||||
# These are generated dynamically from the snapshots present + the active
|
||||
# SERVER_SECURITY_MODE, so there can be millions of them and they can NEVER be
|
||||
# enumerated. The proxy therefore matches by SUFFIX WILDCARD only.
|
||||
#
|
||||
# TLS at that scale:
|
||||
# On-demand / per-host cert issuance does not scale to millions of subdomains
|
||||
# (and would blow ACME rate limits), so for public deployments we serve a
|
||||
# SINGLE `*.<base>` wildcard cert obtained once via DNS-01 by the lego sidecar
|
||||
# (see bin/proxy_lego.sh). One cert covers unlimited snap-* subdomains.
|
||||
#
|
||||
# Configure via env (see .env.proxy.example):
|
||||
# Local (default): serves *.archivebox.localhost via Caddy's internal CA.
|
||||
# Public: ARCHIVEBOX_PROXY_SITE="*.archive.example.com archive.example.com"
|
||||
# ARCHIVEBOX_PROXY_TLS="/certs/wildcard.crt /certs/wildcard.key"
|
||||
#
|
||||
# NOTE: a single-label wildcard (`*.<base>`) does not cover multi-label
|
||||
# original-domain replay hosts like `www.example.com.<base>`; those are a
|
||||
# secondary feature and are reachable via their single-label `snap-<id>.<base>`
|
||||
# host instead. The primary snapshot replay host IS single-label and fully covered.
|
||||
{
|
||||
# ACME contact email (only used if Caddy itself ever manages a cert; the
|
||||
# internal CA and mounted-cert paths ignore it).
|
||||
email {$ARCHIVEBOX_PROXY_ACME_EMAIL:archivebox@localhost}
|
||||
}
|
||||
|
||||
{$ARCHIVEBOX_PROXY_SITE:*.archivebox.localhost archivebox.localhost} {
|
||||
# internal -> Caddy's local CA (good for *.archivebox.localhost dev)
|
||||
# /certs/c /certs/k -> the *.<base> wildcard cert from the lego sidecar
|
||||
tls {$ARCHIVEBOX_PROXY_TLS:internal}
|
||||
|
||||
encode zstd gzip
|
||||
|
||||
# reverse_proxy preserves the inbound Host header and automatically sets
|
||||
# X-Forwarded-Proto / -For / -Host upstream. ArchiveBox's HostRoutingMiddleware
|
||||
# routes by Host, and REVERSE_PROXY_TRUST_FORWARDED_PROTO=True makes it honor
|
||||
# the forwarded scheme so secure cookies + https links work end-to-end.
|
||||
reverse_proxy archivebox:8000
|
||||
}
|
||||
@ -4,6 +4,14 @@ In this folder are some example config files you can use for setting up ArchiveB
|
||||
|
||||
E.g. see `nginx.conf` for an example nginx config to serve your archive with SSL, or `fly.toml` for an example deployment to the Fly.io hosting platform.
|
||||
|
||||
For the recommended, batteries-included reverse proxy, see `Caddyfile` — it's used by
|
||||
the opt-in `docker-compose.proxy.yml` overlay to wildcard-route every dynamically
|
||||
generated snapshot/role subdomain to ArchiveBox and terminate TLS. Locally it serves
|
||||
`*.archivebox.localhost` via Caddy's internal CA; publicly it serves a single
|
||||
`*.<your-domain>` wildcard cert obtained via DNS-01 by the `goacme/lego` sidecar
|
||||
(`../bin/proxy_lego.sh`), which covers unlimited `snap-*` subdomains with one cert and
|
||||
supports ~150 DNS providers without any per-provider code. See `../.env.proxy.example`.
|
||||
|
||||
Please contribute your etc files here! Example contributions
|
||||
|
||||
- supervisord config
|
||||
|
||||
Loading…
Reference in New Issue
Block a user