Files
PacketStation/deploy/compose.yaml
T

226 lines
7.4 KiB
YAML
Raw Normal View History

# Packet Station base stack: postgres, a one-shot migrate, the app and the worker.
# One file for test and production. The env file picks the stack.
#
# Production on docker-1:
# docker compose -p packetstation --env-file /etc/packetstation/prod/stack.env \
# -f deploy/compose.yaml -f deploy/compose.proxy.yaml up -d
# Test on docker-1:
# docker compose -p packetstation-test --env-file ~/packetstation/test/stack.env \
# -f deploy/compose.yaml -f deploy/compose.proxy.yaml up -d
#
# No service publishes a port. NPM reaches the app over npm_default (compose.proxy.yaml).
# PostgreSQL is only on the internal backend network.
#
# ClamAV is optional. Set CLAMAV_HOST=packetstation-clamav in the env file and add `--profile clamav`
# to every `docker compose` command of that stack. Without the profile the clamav service does not run.
# The project name comes from the env file so prod and test can never share volumes by accident.
# `docker compose -p` still wins when given.
name: ${PROJECT_NAME:?set PROJECT_NAME in the stack env file}
x-logging: &logging
driver: json-file
options:
max-size: "10m"
max-file: "5"
# Hardening shared by the three image services. The root filesystem is read-only;
# /tmp is a small tmpfs for Python temp files and the worker beat file.
x-hardening: &hardening
read_only: true
tmpfs:
- /tmp:size=64m,mode=1777
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
services:
postgres:
# postgres:16-bookworm pinned by digest (multi-arch index, looked up 2026-10-15).
# To refresh: docker buildx imagetools inspect postgres:16-bookworm
# and copy the top-level "Digest:" line here. Test on the test stack first.
image: postgres:16-bookworm@sha256:0ea6700a3b4f0ae6ce746519073558aed4d88a79d8d07622a9a644946c7319c4
# uid 999 is the image's postgres user. Group 10001 lets it read the
# root:10001 0440 secret files without running as root.
user: "999:10001"
environment:
POSTGRES_DB: postgres # 01-roles.sh creates the packetstation database.
POSTGRES_USER: postgres
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_superuser_password
PGDATA: /var/lib/postgresql/data/pgdata
TZ: UTC
secrets:
- postgres_superuser_password
- postgres_migrate_password
- postgres_app_password
volumes:
- pgdata:/var/lib/postgresql/data
- ./postgres/initdb:/docker-entrypoint-initdb.d:ro
networks:
backend:
aliases:
- packetstation-postgres # PG_HOST. Unique so it cannot collide with another stack.
shm_size: 256m
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres -h 127.0.0.1"]
interval: 10s
timeout: 5s
retries: 6
start_period: 30s
restart: unless-stopped
logging: *logging
migrate:
image: ${IMAGE:?set IMAGE in the stack env file}
command: ["migrate"]
env_file:
- ${ENV_FILE:?set ENV_FILE in the stack env file}
environment:
# Fixed in-container paths. These win over the env file, where BACKUP_DIR is the host path.
ASSETS_DIR: /data/assets
BACKUP_DIR: /backups
STATIC_DIR: /app/static
secrets:
- postgres_migrate_password
- postgres_app_password
networks:
backend: {}
depends_on:
postgres:
condition: service_healthy
<<: *hardening
restart: "no"
logging: *logging
app:
image: ${IMAGE:?set IMAGE in the stack env file}
command: ["app"]
env_file:
- ${ENV_FILE:?set ENV_FILE in the stack env file}
environment:
# Fixed in-container paths. These win over the env file, where BACKUP_DIR is the host path.
ASSETS_DIR: /data/assets
BACKUP_DIR: /backups
STATIC_DIR: /app/static
secrets:
- postgres_app_password
- ldap_bind_password
- session_key
- seal_key
volumes:
- assets:/data/assets
- ${CA_BUNDLE_FILE:?set CA_BUNDLE_FILE in the stack env file}:/etc/ssl/pesco/ca.pem:ro
networks:
backend: {}
egress: {}
depends_on:
migrate:
condition: service_completed_successfully
healthcheck:
test: ["CMD", "packetstation", "healthcheck", "app"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
<<: *hardening
mem_limit: 1g
restart: unless-stopped
logging: *logging
worker:
image: ${IMAGE:?set IMAGE in the stack env file}
command: ["worker"]
env_file:
- ${ENV_FILE:?set ENV_FILE in the stack env file}
environment:
# Fixed in-container paths. These win over the env file, where BACKUP_DIR is the host path.
ASSETS_DIR: /data/assets
BACKUP_DIR: /backups
STATIC_DIR: /app/static
secrets:
- postgres_app_password
- visualkit_password
- mfileskit_password
- ldap_bind_password
- seal_key
volumes:
- assets:/data/assets
- ${CA_BUNDLE_FILE:?set CA_BUNDLE_FILE in the stack env file}:/etc/ssl/pesco/ca.pem:ro
- ${BACKUP_DIR:?set BACKUP_DIR in the stack env file}:/backups:ro
networks:
backend: {}
egress: {}
depends_on:
migrate:
condition: service_completed_successfully
healthcheck:
test: ["CMD", "packetstation", "healthcheck", "worker"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s
<<: *hardening
mem_limit: 1g
restart: unless-stopped
logging: *logging
clamav:
# Optional virus scanner for uploads (profile clamav). clamd listens on 3310 on the backend network.
# Pinned to the 1.4 minor tag, not a digest: the tag follows the 1.4 patch releases.
# To pin a digest: docker buildx imagetools inspect clamav/clamav:1.4 and use the top-level "Digest:".
image: clamav/clamav:1.4
profiles: ["clamav"]
environment:
CLAMAV_NO_MILTERD: "true"
volumes:
- clamav-db:/var/lib/clamav # Signatures. The first start downloads them (a few minutes).
networks:
backend:
aliases:
- packetstation-clamav # CLAMAV_HOST. Unique so it cannot collide with another stack.
egress: {} # freshclam needs the internet for signature updates.
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD", "clamdcheck.sh"]
interval: 60s
timeout: 10s
retries: 3
start_period: 10m # Room for the first signature download and load.
mem_limit: 2g
restart: unless-stopped
logging: *logging
networks:
backend:
internal: true # No route out. postgres lives only here.
egress:
driver: bridge # Outbound to VISUAL, M-Files, LDAP and SMTP.
volumes:
pgdata: {}
assets: {}
clamav-db: {}
# Host files, root:10001 mode 0440, one value per file, no trailing carriage return.
secrets:
postgres_superuser_password:
file: ${SECRETS_DIR:?set SECRETS_DIR in the stack env file}/postgres_superuser_password
postgres_migrate_password:
file: ${SECRETS_DIR}/postgres_migrate_password
postgres_app_password:
file: ${SECRETS_DIR}/postgres_app_password
visualkit_password:
file: ${SECRETS_DIR}/visualkit_password
mfileskit_password:
file: ${SECRETS_DIR}/mfileskit_password
ldap_bind_password:
file: ${SECRETS_DIR}/ldap_bind_password
session_key:
file: ${SECRETS_DIR}/session_key
seal_key:
file: ${SECRETS_DIR}/seal_key