CI / verify (pull_request) Successful in 1m8s
CI / infrastructure-smoke (pull_request) Successful in 35s
CI / migration-smoke (pull_request) Successful in 1m49s
CI / package-release (pull_request) Successful in 35s
CI / image-build (pull_request) Successful in 50s
CI / deploy-smoke (same-origin) (pull_request) Successful in 43s
CI / deploy-smoke (split) (pull_request) Successful in 2m19s
CI / package-publication (pull_request) Successful in 52s
170 lines
6.6 KiB
YAML
170 lines
6.6 KiB
YAML
# Ekklesia's reference self-hosted deployment: the base project (ADR 0044).
|
|
#
|
|
# `api`, `postgres`, and `object-storage`, plus the operator-only `migrate` service. Nothing here
|
|
# publishes a port. Add `compose.proxy.yaml` for the reference TLS proxy, and `compose.tunnel.yaml`
|
|
# on top of that for a Cloudflare Tunnel connector; or attach `api` and `object-storage` to an
|
|
# existing proxy's network yourself (deploy/README.md).
|
|
#
|
|
# Non-secret settings come from `deploy/.env` (copy `env/compose.env.example`). Secrets live in
|
|
# root-owned, mode-600 files under EKKLESIA_ENV_DIR, one per service, so no container receives
|
|
# another service's credentials. TLS material comes from `scripts/init-tls.sh`.
|
|
|
|
name: ekklesia
|
|
|
|
x-ekklesia-hardening: &ekklesia-hardening
|
|
init: true
|
|
read_only: true
|
|
tmpfs:
|
|
- /tmp
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
|
|
x-ekklesia-app: &ekklesia-app
|
|
<<: *ekklesia-hardening
|
|
image: ${EKKLESIA_IMAGE:?EKKLESIA_IMAGE is required, for example registry.example.org/ekklesia:0.1.0}
|
|
env_file:
|
|
- ${EKKLESIA_ENV_DIR:-/etc/ekklesia}/ekklesia.env
|
|
environment:
|
|
# Core's object-storage client trusts Node's default CA set. Adding the installation-private CA
|
|
# to it, from the file below, is what lets the API verify `object-storage`'s certificate
|
|
# without any storage TLS exception. The database CA travels separately, as PEM text in
|
|
# EKKLESIA_DATABASE_CA, and is the only CA the database connection trusts.
|
|
NODE_EXTRA_CA_CERTS: /run/ekklesia/tls/ca.crt
|
|
# Installed modules (ADR 0048). The image sets the same value; stating it beside the mount below
|
|
# keeps the variable and the mount target from ever drifting apart.
|
|
EKKLESIA_MODULES_DIR: /var/lib/ekklesia/modules
|
|
volumes:
|
|
- ${EKKLESIA_TLS_DIR:-/etc/ekklesia/tls}/ca.crt:/run/ekklesia/tls/ca.crt:ro
|
|
# Installation-owned module storage, outside the image (ADR 0048). Read-only: Ekklesia never
|
|
# writes installed code. The host directory must already exist; a missing one stops the
|
|
# container from starting instead of Compose quietly creating an empty, root-owned directory.
|
|
- type: bind
|
|
source: ${EKKLESIA_MODULES_HOST_DIR:-/var/lib/ekklesia/modules}
|
|
target: /var/lib/ekklesia/modules
|
|
read_only: true
|
|
bind:
|
|
create_host_path: false
|
|
networks:
|
|
data: {}
|
|
storage: {}
|
|
egress: {}
|
|
|
|
services:
|
|
api:
|
|
<<: *ekklesia-app
|
|
restart: unless-stopped
|
|
# The image's own HEALTHCHECK (GET /api/v1/health/ready) applies unchanged.
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
object-storage:
|
|
condition: service_healthy
|
|
|
|
# Operator commands only. `profiles` keeps `docker compose up` from ever starting it; it runs only
|
|
# through `docker compose run --rm migrate …`. The default command is the read-only plan.
|
|
migrate:
|
|
<<: *ekklesia-app
|
|
profiles:
|
|
- ops
|
|
restart: "no"
|
|
healthcheck:
|
|
disable: true
|
|
command: ["node", "core/dist/cli/plan-migrations.js"]
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
object-storage:
|
|
condition: service_healthy
|
|
|
|
postgres:
|
|
image: postgres:18.4-alpine3.24@sha256:9a8afca54e7861fd90fab5fdf4c42477a6b1cb7d293595148e674e0a3181de15
|
|
restart: unless-stopped
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
env_file:
|
|
- ${EKKLESIA_ENV_DIR:-/etc/ekklesia}/postgres.env
|
|
environment:
|
|
POSTGRES_INITDB_ARGS: --data-checksums --auth-host=scram-sha-256 --auth-local=trust
|
|
TZ: UTC
|
|
# TLS with the installation-private CA's server certificate for the name `postgres`, TLS 1.3
|
|
# only, and a host-based authentication file that rejects every non-TLS network connection.
|
|
# The API connects with verify-full; there is no plaintext path to fall back to.
|
|
command:
|
|
- postgres
|
|
- -c
|
|
- ssl=on
|
|
- -c
|
|
- ssl_cert_file=/run/ekklesia/tls/postgres.crt
|
|
- -c
|
|
- ssl_key_file=/run/ekklesia/tls/postgres.key
|
|
- -c
|
|
- ssl_min_protocol_version=TLSv1.3
|
|
- -c
|
|
- hba_file=/etc/ekklesia/pg_hba.conf
|
|
volumes:
|
|
- postgres-data:/var/lib/postgresql
|
|
- ${EKKLESIA_TLS_DIR:-/etc/ekklesia/tls}/postgres.crt:/run/ekklesia/tls/postgres.crt:ro
|
|
- ${EKKLESIA_TLS_DIR:-/etc/ekklesia/tls}/postgres.key:/run/ekklesia/tls/postgres.key:ro
|
|
- ./postgres/pg_hba.conf:/etc/ekklesia/pg_hba.conf:ro
|
|
# Only `data`: never a proxy-facing network, and no route out of the host.
|
|
networks:
|
|
data: {}
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 12
|
|
start_period: 20s
|
|
|
|
object-storage:
|
|
image: chrislusf/seaweedfs:4.28@sha256:d071979843142451d41566bc09f6adaa36902c112683d900a417247c82e9c3c1
|
|
restart: unless-stopped
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
env_file:
|
|
- ${EKKLESIA_ENV_DIR:-/etc/ekklesia}/object-storage.env
|
|
environment:
|
|
TZ: UTC
|
|
# The S3 API over HTTPS on 8443, with the installation-private CA's certificate for the name
|
|
# `object-storage`: production storage must be https (ADR 0030), for the API and for the proxy.
|
|
command:
|
|
- mini
|
|
- -dir=/data
|
|
# A bucket is a SeaweedFS collection and claims a block of volumes. The ~1 GB automatic
|
|
# volume size leaves modest hosts with room for only a few collections; after that, writes
|
|
# stall instead of failing. This changes allocation granularity, not object or total capacity.
|
|
- -master.volumeSizeLimitMB=32
|
|
- -s3.port.https=8443
|
|
- -s3.cert.file=/run/ekklesia/tls/object-storage.crt
|
|
- -s3.key.file=/run/ekklesia/tls/object-storage.key
|
|
volumes:
|
|
- object-storage-data:/data
|
|
- ${EKKLESIA_TLS_DIR:-/etc/ekklesia/tls}/object-storage.crt:/run/ekklesia/tls/object-storage.crt:ro
|
|
- ${EKKLESIA_TLS_DIR:-/etc/ekklesia/tls}/object-storage.key:/run/ekklesia/tls/object-storage.key:ro
|
|
networks:
|
|
storage: {}
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:9333/cluster/status"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 18
|
|
start_period: 15s
|
|
|
|
networks:
|
|
# The database network: postgres, api, and migrate. Internal, so nothing on it reaches beyond the
|
|
# host and no proxy is ever attached to it.
|
|
data:
|
|
internal: true
|
|
# The storage network: object-storage, api, and migrate. Internal for the same reason; browsers
|
|
# reach storage only through a proxy that joins it on `edge`.
|
|
storage:
|
|
internal: true
|
|
# Outbound access for api and migrate (an SMTP relay, for example). Nothing listens on it.
|
|
egress: {}
|
|
|
|
volumes:
|
|
postgres-data:
|
|
object-storage-data:
|