-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.prod.yml
More file actions
125 lines (117 loc) · 6.67 KB
/
Copy pathdocker-compose.prod.yml
File metadata and controls
125 lines (117 loc) · 6.67 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
# docker-compose.prod — production overlay for a REAL network-edge host (real cert, 443 + QUIC).
# Use WITH the base file: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# Requires a .env with KITSUNE_DOMAIN=your.domain and KITSUNE_ACME_EMAIL=you@example.com. See docs/deploy.md.
services:
detector:
# PRIVACY: KITSUNE_DB=:memory: — the public site holds a visitor's signals only in memory long enough to
# compute and show the verdict, then forgets them. NOTHING is written to disk; the store is gone on restart.
# No visitor fingerprints are captured, retained, sold, shared, or used to track across visits. See
# docs/privacy.md. (A PRIVATE research instance can opt into persistence with KITSUNE_DB=/data/kitsune.db +
# a volume — but that DOES retain fingerprints, so it is for your own evader traffic only, never a public site.)
# KITSUNE_ADMIN_TOKEN (from .env, optional) gates the operator endpoints /session,/verdict,/scoreboard;
# empty/unset leaves them open (dev behaviour) — SET it on a public deploy. (The API docs at /api and
# /openapi.json are public by design and stay served regardless.)
# KITSUNE_GEOIP_DIR / KITSUNE_IPREP_DIR point at read-only dirs of public GeoIP / IP-reputation lists
# (additive enrichment of the IP the connection already carries; no visitor data). Absent -> in-image seeds.
# Run the image CI published to GHCR (built + pushed by build-push.yml when a GitHub Release is published
# — release-please cuts the release when its release PR merges), so `docker compose ... pull && up -d`
# ships the latest release and Watchtower (below) auto-updates it.
# The base file's `build:` is still there for a from-source build if you'd rather (`up -d --build`).
image: ghcr.io/datascry/kitsune-detector:latest
labels:
- "com.centurylinklabs.watchtower.enable=true" # Watchtower auto-updates this container (label-scoped)
environment:
KITSUNE_DB: ":memory:"
KITSUNE_ADMIN_TOKEN: ${KITSUNE_ADMIN_TOKEN:-}
KITSUNE_GEOIP_DIR: /geoip
KITSUNE_IPREP_DIR: /iprep
volumes:
- ./geoip:/geoip:ro
- ./iprep:/iprep:ro
restart: unless-stopped
# One-shot IP-reputation refresh. The detector mounts ./iprep READ-ONLY at runtime; this companion
# mounts the SAME host dir READ-WRITE at /out and writes the refreshed lists there — so the operator runs
# it with no flags to remember (the read-only-vs-read-write split is baked in here, not passed by hand):
# docker compose -f docker-compose.yml -f docker-compose.prod.yml run --rm --build iprep-refresh
# The `tools` profile keeps it OUT of `up` (it only runs via explicit `run`). Then restart the detector to
# pick up the new lists. Cron it monthly; see docs/deploy.md.
iprep-refresh:
profiles: ["tools"]
build:
context: .
dockerfile: detector/Dockerfile
environment:
KITSUNE_IPREP_DIR: /out
volumes:
- ./iprep:/out
command: ["uv", "run", "python", "-m", "kitsune_detector.ip_reputation_refresh"]
restart: "no"
# One-shot GEO refresh — the keyless twin of iprep-refresh. The detector mounts ./geoip READ-ONLY at
# runtime; this companion mounts the SAME host dir READ-WRITE at /out and pulls DB-IP's free Lite City+ASN
# MMDBs (CC BY 4.0, NO licence key) there — so geo goes live with one command and no operator secret:
# docker compose -f docker-compose.yml -f docker-compose.prod.yml run --rm --build geo-refresh
# The `tools` profile keeps it OUT of `up`. Then restart the detector to pick up the DBs. Cron it monthly
# (DB-IP publishes a new edition on the 1st); see docs/deploy.md.
geo-refresh:
profiles: ["tools"]
build:
context: .
dockerfile: detector/Dockerfile
environment:
KITSUNE_GEOIP_DIR: /out
volumes:
- ./geoip:/out
command: ["uv", "run", "python", "-m", "kitsune_detector.geo_refresh"]
restart: "no"
edge:
# Terminate TLS with the real Let's Encrypt cert (loaded by the edge's loadCert via these env paths) and
# serve on the public ports. CAP_NET_RAW (for the TCP/IP SYN sniffer) + user:root are inherited from the
# base file. The edge MUST be the direct TLS endpoint — do NOT put a proxying CDN/LB in front (it would
# re-terminate TLS and destroy the JA3/JA4, HTTP/2 and TCP fingerprints). DNS A/AAAA -> this host directly.
image: ghcr.io/datascry/kitsune-edge:latest
labels:
- "com.centurylinklabs.watchtower.enable=true"
environment:
KITSUNE_EDGE_ADDR: "0.0.0.0:443"
KITSUNE_TLS_CERT: "/certs/live/${KITSUNE_DOMAIN}/fullchain.pem"
KITSUNE_TLS_KEY: "/certs/live/${KITSUNE_DOMAIN}/privkey.pem"
ports:
- "443:443" # TLS + HTTP/2
- "443:443/udp" # QUIC / HTTP-3 (Alt-Svc advertised; needs the real cert)
volumes:
- letsencrypt:/certs:ro
restart: unless-stopped
# The arena challenge-gate service (relayed by the detector at /arena/*). Same GHCR image + auto-update.
arena:
image: ghcr.io/datascry/kitsune-arena:latest
labels:
- "com.centurylinklabs.watchtower.enable=true"
restart: unless-stopped
# Pull-based continuous deploy: Watchtower polls GHCR every 5 min and, when CI publishes a new image for a
# LABELLED container (detector/edge/arena above), pulls it and recreates the container — so a published
# release reaches the live site with no manual redeploy. Label-scoped, so it never touches certbot or itself.
watchtower:
image: containrrr/watchtower:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock # NOTE: socket access is root-equivalent (single-box trade-off)
environment:
WATCHTOWER_LABEL_ENABLE: "true" # only the labelled containers, not certbot/watchtower itself
WATCHTOWER_POLL_INTERVAL: "300" # check GHCR every 5 min
WATCHTOWER_CLEANUP: "true" # remove the superseded image after a successful update
restart: unless-stopped
# Let's Encrypt via certbot (HTTP-01 standalone on :80). Initial issuance is a one-shot `run` (docs/deploy.md);
# this long-running service renews every 12h. The edge loads the cert at startup, so after a renewal restart
# the edge to pick it up: `docker compose ... restart edge` (a monthly cron is plenty). 80 must be open.
certbot:
image: certbot/certbot:latest
ports:
- "80:80"
volumes:
- letsencrypt:/etc/letsencrypt
entrypoint: /bin/sh
command: -c "trap exit TERM; while :; do certbot renew --standalone --quiet; sleep 12h & wait $${!}; done"
restart: unless-stopped
volumes:
# No detector-data volume: the detector runs in-memory (KITSUNE_DB=:memory:), so there is nothing to persist
# and a public visitor's signals never touch the disk. See docs/privacy.md.
letsencrypt: