-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdocker-compose.example.yml
More file actions
113 lines (108 loc) · 5.05 KB
/
Copy pathdocker-compose.example.yml
File metadata and controls
113 lines (108 loc) · 5.05 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
# Artefactor — the app and its isolated thumbnail renderer (S37, AH29).
#
# Both services run the SAME image in different roles (ARTEFACTOR_ROLE). The app
# holds the data and the secrets; the renderer holds nothing and runs untrusted
# artefact HTML in sandboxed Chromium. Read docs/renderer-isolation.md before
# changing anything below — most lines here are a defence, not a preference.
#
# 1. cp deploy/docker-compose.example.yml docker-compose.yml \
# && cp deploy/chromium-seccomp.json . # both files, then edit the env
# (`security_opt` resolves the profile relative to the Compose project
# directory, so the two must sit side by side — running the example in place
# with `docker compose -f deploy/docker-compose.example.yml` works as well.)
# 2. sudo ./deploy/renderer-egress.sh # host firewall rules
# 3. docker compose up -d
# 4. docs/renderer-isolation.md § Verify a deployment
#
# Without step 2 the app cannot reach the renderer (the link network denies
# container-to-container traffic until the script allows exactly app → renderer),
# so thumbnails stay placeholders instead of failing open.
name: artefactor
services:
app:
image: ghcr.io/hiddenvalues/artefactor:latest
restart: always
# Loopback only: put a reverse proxy (TLS) on the host in front of it, and
# never publish 3000 on a public interface. The app reads X-Forwarded-For
# only from a trusted proxy (IA8): by default loopback and private ranges,
# which covers a proxy on this host or on this project's network. A proxy
# elsewhere needs ARTEFACTOR_TRUSTED_PROXIES (docs/deployment.md §5). A proxy
# running in Compose instead joins this project's network and needs no port here.
ports:
- "127.0.0.1:3000:3000"
environment:
# The isolated renderer. Unset this and thumbnails are simply off (AH25).
ARTEFACTOR_RENDERER_URL: http://renderer:3001
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?set it, e.g. openssl rand -hex 32}
BETTER_AUTH_URL: ${BETTER_AUTH_URL:?the public https URL}
# Sign-in methods (S38 / IA7): production needs at least one, so set the
# Google pair *or* AUTH_EMAIL_PASSWORD=true — the server refuses to boot
# with neither. Empty means "unset", i.e. take the default.
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
AUTH_EMAIL_PASSWORD: ${AUTH_EMAIL_PASSWORD:-}
# Account creation (IA 4). Default: closed when production has the
# unverified email+password path open, otherwise open.
AUTH_ALLOW_SIGNUP: ${AUTH_ALLOW_SIGNUP:-}
AUTH_ALLOWED_EMAIL_DOMAINS: ${AUTH_ALLOWED_EMAIL_DOMAINS:?your org domain(s)}
volumes:
# The SQLite DB, the artefact payloads and their thumbnails.
- artefactor-data:/data
networks:
- artefactor-link
depends_on:
- renderer
renderer:
image: ghcr.io/hiddenvalues/artefactor:latest
# Every render is one job in a fresh container: the process exits 0 when it is
# done (after ≥ 10 s of uptime, which keeps Docker's restart delay at ~100 ms)
# and this policy starts a new one with an empty tmpfs.
restart: always
environment:
ARTEFACTOR_ROLE: renderer
# No secrets, no database, no storage paths — the renderer's own startup
# self-check refuses to run in production if any of them appear.
PORT: "3001"
NODE_ENV: production
# Unprivileged, and it stays that way: no capabilities, no setuid escalation.
user: node
cap_drop: [ALL]
security_opt:
- no-new-privileges:true
# Docker's default profile plus clone/unshare/chroot — exactly what
# Chromium's unprivileged sandbox needs, and nothing that needs a capability.
- seccomp=./chromium-seccomp.json
# Nothing on disk survives a job: the root filesystem is read-only and the
# writable paths are tmpfs (Chromium's profile, its temp files and $HOME).
read_only: true
tmpfs:
- /tmp:mode=1777,size=512m
- /home/node:mode=0700,uid=1000,gid=1000,size=64m
# No volume, no published port: nothing to read, and unreachable from outside.
networks:
- artefactor-link
- renderer-egress
mem_limit: 1g
cpus: 1.0
pids_limit: 256
volumes:
artefactor-data:
networks:
# App ↔ renderer. `internal` keeps it off the internet; icc=false means even the
# two containers on it cannot talk until deploy/renderer-egress.sh allows the one
# direction that is wanted (app → renderer:3001), so a compromised renderer has
# no path back to the app.
artefactor-link:
internal: true
driver_opts:
com.docker.network.bridge.name: br-art-link
com.docker.network.bridge.enable_icc: "false"
# The renderer's way out: the public internet only (CDN CSS, fonts, images), with
# every private and link-local range dropped by deploy/renderer-egress.sh. The
# fixed bridge name is what those rules match on.
renderer-egress:
driver_opts:
com.docker.network.bridge.name: br-art-egress
ipam:
config:
- subnet: 172.31.240.0/24