- update.py builds target commits in temporary detached worktrees with temporary image tags; checkouts and runtime tags move only after the API fence and quiescence check pass, so refused/build-only runs keep --verify-only passing. - Activation includes the execution profile (worker/watchdog restart under the fence) and verifies every service container runs the built image before writing approved-commits.env. - Compose: bounded local logging, API healthcheck via `otche healthcheck`, web healthcheck and healthy API dependency, env-driven worker limits. - TLS example: request-time upstream resolution, gzip, immutable assets, headers not duplicated on /api. - README: new update flow, sizing for parallel runs, fenced backup and restore drill.
158 lines
4.8 KiB
YAML
158 lines
4.8 KiB
YAML
name: otche
|
|
x-logging: &logging
|
|
driver: local
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "5"
|
|
services:
|
|
postgres:
|
|
logging: *logging
|
|
image: postgres:17-alpine
|
|
user: "70:70"
|
|
read_only: true
|
|
tmpfs: ["/tmp:size=32m,noexec,nosuid", "/var/run/postgresql:size=8m,noexec,nosuid"]
|
|
cap_drop: [ALL]
|
|
security_opt: [no-new-privileges:true]
|
|
environment:
|
|
POSTGRES_DB: otche
|
|
POSTGRES_USER: otche
|
|
POSTGRES_PASSWORD_FILE: /run/secrets/postgres-password
|
|
secrets: [postgres-password]
|
|
volumes: [postgres-data:/var/lib/postgresql/data]
|
|
healthcheck:
|
|
test: [CMD-SHELL, "pg_isready -U otche -d otche"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 20
|
|
restart: unless-stopped
|
|
networks: [private]
|
|
mem_limit: 768m
|
|
storage-init:
|
|
logging: *logging
|
|
image: debian:bookworm-slim
|
|
user: "0:0"
|
|
command: [sh, -ec, "chown 10001:10001 /var/lib/otche && chmod 0700 /var/lib/otche"]
|
|
volumes: [artifacts:/var/lib/otche]
|
|
network_mode: none
|
|
cap_drop: [ALL]
|
|
cap_add: [CHOWN, FOWNER]
|
|
security_opt: [no-new-privileges:true]
|
|
migrate:
|
|
logging: *logging
|
|
build: {context: ../otche-backend, target: api}
|
|
command: [migrate]
|
|
environment: {DATABASE_URL_FILE: /run/secrets/database-url}
|
|
secrets: [database-url]
|
|
depends_on:
|
|
postgres: {condition: service_healthy}
|
|
networks: [private]
|
|
read_only: true
|
|
cap_drop: [ALL]
|
|
security_opt: [no-new-privileges:true]
|
|
api:
|
|
logging: *logging
|
|
build: {context: ../otche-backend, target: api}
|
|
environment:
|
|
DATABASE_URL_FILE: /run/secrets/database-url
|
|
PUBLIC_ORIGIN: ${PUBLIC_ORIGIN:?Set PUBLIC_ORIGIN}
|
|
ALLOW_INSECURE_HTTP: ${ALLOW_INSECURE_HTTP:-false}
|
|
ARTIFACT_ROOT: /var/lib/otche
|
|
MAX_UPLOAD_BYTES: ${MAX_UPLOAD_BYTES:-536870912}
|
|
MAX_OWNER_BYTES: ${MAX_OWNER_BYTES:-8589934592}
|
|
MAX_QUEUED_JOBS: ${MAX_QUEUED_JOBS:-20}
|
|
secrets: [database-url]
|
|
volumes: [artifacts:/var/lib/otche]
|
|
depends_on:
|
|
migrate: {condition: service_completed_successfully}
|
|
storage-init: {condition: service_completed_successfully}
|
|
healthcheck:
|
|
test: ["CMD", "otche", "healthcheck"]
|
|
interval: 10s
|
|
timeout: 3s
|
|
retries: 6
|
|
start_period: 10s
|
|
restart: unless-stopped
|
|
networks: [private, edge]
|
|
read_only: true
|
|
tmpfs: ["/tmp:size=32m,noexec,nosuid"]
|
|
cap_drop: [ALL]
|
|
security_opt: [no-new-privileges:true]
|
|
mem_limit: 256m
|
|
cpus: 1.0
|
|
worker:
|
|
logging: *logging
|
|
build: {context: ../otche-backend, target: worker}
|
|
command: [worker]
|
|
profiles: [execution]
|
|
environment:
|
|
DATABASE_URL_FILE: /run/secrets/database-url
|
|
WORKER_CONFIG_FILE: /run/otche/config.json
|
|
ARTIFACT_ROOT: /var/lib/otche
|
|
secrets: [database-url]
|
|
volumes:
|
|
- artifacts:/var/lib/otche
|
|
- ./secrets/worker:/run/otche:ro
|
|
depends_on:
|
|
migrate: {condition: service_completed_successfully}
|
|
storage-init: {condition: service_completed_successfully}
|
|
restart: unless-stopped
|
|
networks: [private, execution]
|
|
read_only: true
|
|
tmpfs: ["/tmp:size=128m,noexec,nosuid"]
|
|
cap_drop: [ALL]
|
|
security_opt: [no-new-privileges:true]
|
|
mem_limit: ${WORKER_MEM_LIMIT:-1536m}
|
|
cpus: ${WORKER_CPUS:-2.0}
|
|
pids_limit: ${WORKER_PIDS_LIMIT:-128}
|
|
stop_grace_period: 60s
|
|
watchdog:
|
|
logging: *logging
|
|
build: {context: ../otche-backend, target: worker}
|
|
command: [watchdog]
|
|
profiles: [execution]
|
|
environment:
|
|
DATABASE_URL_FILE: /run/secrets/database-url
|
|
WORKER_CONFIG_FILE: /run/otche/config.json
|
|
secrets: [database-url]
|
|
volumes: ["./secrets/worker:/run/otche:ro"]
|
|
depends_on:
|
|
migrate: {condition: service_completed_successfully}
|
|
restart: unless-stopped
|
|
networks: [private, execution]
|
|
read_only: true
|
|
cap_drop: [ALL]
|
|
security_opt: [no-new-privileges:true]
|
|
mem_limit: 128m
|
|
cpus: 0.25
|
|
web:
|
|
logging: *logging
|
|
build: {context: ../otche-frontend}
|
|
ports: ["${BIND_ADDRESS:-127.0.0.1}:${WEB_PORT:-8088}:8080"]
|
|
depends_on:
|
|
api: {condition: service_healthy}
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:8080/ || wget -q --spider --no-check-certificate https://127.0.0.1:8443/"]
|
|
interval: 10s
|
|
timeout: 3s
|
|
retries: 6
|
|
start_period: 10s
|
|
restart: unless-stopped
|
|
networks: [edge]
|
|
read_only: true
|
|
tmpfs: ["/tmp:size=32m,noexec,nosuid", "/var/cache/nginx:size=32m,noexec,nosuid", "/var/run:size=1m,noexec,nosuid"]
|
|
cap_drop: [ALL]
|
|
security_opt: [no-new-privileges:true]
|
|
mem_limit: 128m
|
|
secrets:
|
|
postgres-password:
|
|
file: ./secrets/postgres-password
|
|
database-url:
|
|
file: ./secrets/database-url
|
|
volumes:
|
|
postgres-data:
|
|
artifacts:
|
|
networks:
|
|
private: {internal: true}
|
|
edge: {}
|
|
execution: {}
|