155 lines
6.3 KiB
Django/Jinja
155 lines
6.3 KiB
Django/Jinja
# Managed by Ansible — BunkerWeb in all-in-one Docker mode per
|
|
# https://docs.bunkerweb.io/. Verify against your BunkerWeb
|
|
# version at deploy time; this template targets 1.5.x.
|
|
#
|
|
# Three services:
|
|
# bunkerweb — public entry: TLS + WAF, proxies every path to
|
|
# `frontend` (REVERSE_PROXY_HOST)
|
|
# bunkerweb-scheduler — config management agent
|
|
# frontend — plain nginx that serves the SvelteKit static
|
|
# build AND reverse-proxies /v1/, /relay/, /rss/
|
|
# + SSE to the relay + indexer on the host
|
|
# (the frontend/ build context is copied from
|
|
# {{ morphit_repo_path }}/ops/bunkerweb/frontend
|
|
# by tasks/main.yml)
|
|
#
|
|
# Topology: client ──TLS──> bunkerweb ──> frontend ──> host relay/indexer
|
|
#
|
|
# The morphit relay + indexer run on the HOST (not in this compose),
|
|
# reachable from the `frontend` container via host.docker.internal
|
|
# (Linux: host-gateway). They bind on all interfaces (group_vars
|
|
# morphit_*_bind_host) because a loopback-only bind is unreachable
|
|
# from the bridge; UFW (default-deny + the bunkerweb-role bridge
|
|
# allow) keeps the public out.
|
|
|
|
services:
|
|
{% if not (morphit_tor_only | default(false)) %}
|
|
# ── Clearnet TLS edge (BunkerWeb WAF). Omitted for a Tor-only node,
|
|
# which serves its onion straight from the frontend below (no cert, no
|
|
# public 80/443). ─────────────────────────────────────────────────────
|
|
# cp663 #8 — the bw-data named volume is created root-owned, but the
|
|
# scheduler + instance run as UID 101 and must write their config DB
|
|
# there ("Database is not initialized" otherwise). This one-shot init
|
|
# (root) fixes ownership before anything else starts; scheduler
|
|
# depends_on it completing.
|
|
bw-init:
|
|
image: {{ bunkerweb_scheduler_image }}
|
|
container_name: bunkerweb-init
|
|
user: "0:0"
|
|
entrypoint: ["sh", "-c"]
|
|
# (a) cp663 #8 — make the config volume writable by UID 101.
|
|
# (b) cp663 #7 — make the Let's Encrypt cert readable by GID 101
|
|
# (certbot writes it root-only in a 0700 archive dir; the
|
|
# scheduler + instance run as 101 and otherwise serve a
|
|
# self-signed fallback). Runs on every `compose up`, so it also
|
|
# re-asserts perms after a re-create. For RENEWAL and for a
|
|
# DEFERRED (first-online) cert, the same fix lives in the certbot
|
|
# deploy-hook and morphit-first-online respectively.
|
|
command:
|
|
- >-
|
|
chown -R 101:101 /data && chmod -R u+rwX /data;
|
|
chgrp -R 101 /etc/letsencrypt/live /etc/letsencrypt/archive 2>/dev/null || true;
|
|
chmod -R g+rX /etc/letsencrypt/live /etc/letsencrypt/archive 2>/dev/null || true
|
|
restart: "no"
|
|
volumes:
|
|
- bw-data:/data
|
|
- /etc/letsencrypt:/etc/letsencrypt
|
|
networks:
|
|
- bunkerweb_net
|
|
|
|
bunkerweb:
|
|
image: {{ bunkerweb_image }}
|
|
container_name: bunkerweb
|
|
restart: unless-stopped
|
|
ports:
|
|
- "80:8080"
|
|
- "443:8443"
|
|
env_file:
|
|
- ./bunkerweb.env
|
|
volumes:
|
|
- bw-data:/data
|
|
# Let's Encrypt certs from the host.
|
|
- /etc/letsencrypt:/etc/letsencrypt:ro
|
|
extra_hosts:
|
|
# So BunkerWeb can reach the relay + indexer on the host's
|
|
# loopback. Linux: host-gateway resolves to the host's
|
|
# docker-internal IP.
|
|
- "host.docker.internal:host-gateway"
|
|
networks:
|
|
- bunkerweb_net
|
|
depends_on:
|
|
- bunkerweb-scheduler
|
|
|
|
bunkerweb-scheduler:
|
|
image: {{ bunkerweb_scheduler_image }}
|
|
container_name: bunkerweb-scheduler
|
|
restart: unless-stopped
|
|
env_file:
|
|
- ./bunkerweb.env
|
|
volumes:
|
|
- bw-data:/data
|
|
# cp663 #13 — the scheduler (not just the instance) reads
|
|
# CUSTOM_SSL_CERT to validate + ship the cert; without this mount
|
|
# its customcert job logs "not a valid file" and the instance
|
|
# falls back to a self-signed cert.
|
|
- /etc/letsencrypt:/etc/letsencrypt:ro
|
|
# cp663 #10 — BunkerWeb's scheduler manages the instance via the
|
|
# Docker API, so it needs the socket. SECURITY: this is a raw
|
|
# read-only socket bind; the recommended hardening is a read-only
|
|
# docker-socket-proxy (BunkerWeb's `bw-docker` pattern) so a
|
|
# scheduler compromise can't drive the host's Docker — deferred
|
|
# because it needs real-host validation + a bundled proxy image.
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
# The socket is root:docker mode 660; the scheduler user (101) needs
|
|
# the host's docker group to read it. GID resolved by tasks/main.yml.
|
|
group_add:
|
|
- "{{ bunkerweb_docker_gid }}"
|
|
networks:
|
|
- bunkerweb_net
|
|
depends_on:
|
|
bw-init:
|
|
condition: service_completed_successfully
|
|
|
|
{% endif %}
|
|
# Serves the built SvelteKit site + reverse-proxies the API paths to
|
|
# the relay + indexer on the host. BunkerWeb proxies everything here
|
|
# (REVERSE_PROXY_HOST=http://frontend:80). The build context is
|
|
# copied to /etc/bunkerweb/frontend by tasks/main.yml.
|
|
frontend:
|
|
build:
|
|
context: ./frontend
|
|
container_name: morphit-frontend
|
|
restart: unless-stopped
|
|
# cp695 — publish the frontend (the nginx that serves the site AND fans out
|
|
# /v1 -> indexer, /relay -> relay) on a HOST LOOPBACK port so the Tor + I2P
|
|
# hidden services can reach it. They can't go through BunkerWeb (it force-
|
|
# redirects http->https, which Tor/I2P can't follow) and the container isn't
|
|
# otherwise host-published. Loopback-only: never exposed to the LAN/internet.
|
|
ports:
|
|
- "127.0.0.1:{{ morphit_onion_frontend_port | default(8090) }}:80"
|
|
volumes:
|
|
# The SvelteKit production build from the cloned repo, read-only.
|
|
- {{ morphit_repo_path }}/apps/web/build:/usr/share/nginx/html:ro
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
networks:
|
|
- bunkerweb_net
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1/ || exit 1"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
volumes:
|
|
bw-data:
|
|
|
|
networks:
|
|
bunkerweb_net:
|
|
name: bunkerweb_net
|
|
driver: bridge
|
|
ipam:
|
|
config:
|
|
# Pin the CIDR so the operator can hard-code it in
|
|
# MORPHIT_RELAY_TRUSTED_PROXY_IPS without having to
|
|
# re-inspect after a rebuild.
|
|
- subnet: 172.20.0.0/16
|