Files
mobiniqow 212074c5f5
CI / Backend (lint, types, tests) (push) Has been cancelled
CI / Frontend (lint, types, build) (push) Has been cancelled
CI / Docker images build (push) Has been cancelled
start project
2026-09-04 12:52:21 +03:30

132 lines
5.0 KiB
YAML

# =============================================================================
# Production overlay.
#
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
#
# What it changes:
# * Elasticsearch authentication is switched on and every client is given
# credentials.
# * Kibana is no longer published; reach it through an SSH tunnel or an
# authenticating reverse proxy.
# * Containers get CPU/memory limits and no longer seed the dataset on boot.
# * The API is fronted by HTTPS (SECURE_SSL_REDIRECT + HSTS come from
# config/settings/prod.py).
#
# TLS between the containers is intentionally out of scope here: in a real
# deployment the cluster sits behind a service mesh or an internal load
# balancer that terminates it. See docs/SECURITY.md.
# =============================================================================
services:
elasticsearch:
environment:
xpack.security.enabled: "true"
ELASTIC_PASSWORD: ${ELASTIC_PASSWORD:?ELASTIC_PASSWORD is required in production}
healthcheck:
test:
- "CMD-SHELL"
- "curl -fsS -u elastic:${ELASTIC_PASSWORD} 'http://localhost:9200/_cluster/health?wait_for_status=yellow&timeout=5s' >/dev/null || exit 1"
deploy:
resources:
limits: { memory: 2g }
logstash:
environment:
# The Elasticsearch output reads its credentials from the URL.
ELASTICSEARCH_HOSTS: http://elastic:${ELASTIC_PASSWORD}@elasticsearch:9200
deploy:
resources:
limits: { memory: 1g }
kibana:
environment:
ELASTICSEARCH_USERNAME: elastic
ELASTICSEARCH_PASSWORD: ${ELASTIC_PASSWORD}
# Remove the published port: Kibana is reached through a tunnel or proxy.
ports: !reset []
deploy:
resources:
limits: { memory: 1g }
backend:
environment:
DJANGO_SETTINGS_MODULE: config.settings.prod
DJANGO_DEBUG: "false"
SECURE_SSL_REDIRECT: "true"
# Off unless deliberately opted in: /api/docs/ and /api/schema/ publish
# the entire API surface to anonymous callers.
SERVE_API_DOCS: ${SERVE_API_DOCS:-false}
ELASTICSEARCH_USERNAME: elastic
ELASTICSEARCH_PASSWORD: ${ELASTIC_PASSWORD}
# A production container must never mutate data on boot.
SEED_DATASET: "false"
SEED_USERS: "false"
# Nor schema-migrate on boot: with `replicas: 2` below, both containers
# would start `manage.py migrate` at the same moment and race each other
# through the same DDL. Migrations are a deliberate, one-at-a-time
# deploy step instead - `make prod-migrate`, which runs the one-shot
# `backend-migrate` service defined below.
RUN_MIGRATIONS: "false"
deploy:
replicas: 2
resources:
limits: { cpus: "1.0", memory: 768m }
# ---------------------------------------------------------------------------
# One-shot schema migration, run by `make prod-migrate` before rolling out a
# new image. Kept out of the default set by the "migrate" profile so a plain
# `up` never starts it.
# ---------------------------------------------------------------------------
backend-migrate:
extends:
file: docker-compose.yml
service: backend
profiles: ["migrate"]
restart: "no"
entrypoint: []
command: ["python", "manage.py", "migrate", "--noinput"]
environment:
DJANGO_SETTINGS_MODULE: config.settings.prod
DJANGO_DEBUG: "false"
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY:?DJANGO_SECRET_KEY is required}
DATABASE_URL: postgres://${POSTGRES_USER:-linkedin}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-linkedin}
# Migrating must not depend on the search cluster being reachable.
SEARCH_BACKEND: postgres
SECURE_SSL_REDIRECT: "false"
deploy: !reset null
frontend:
deploy:
resources:
limits: { cpus: "0.5", memory: 128m }
postgres:
deploy:
resources:
limits: { memory: 1g }
# =============================================================================
# Data durability
#
# The base compose file declares these as ordinary project volumes, which
# `docker compose down -v` (and therefore `make clean`) deletes without
# asking. Marking them external in production flips that guarantee: Compose
# treats them as owned by the operator, never creates them and - crucially -
# never removes them, so a mistyped teardown cannot destroy the database or
# the search index.
#
# They must exist before the first `make prod`; `make prod-volumes` creates
# them. Back them up with your usual volume/snapshot tooling - external only
# means Compose will not delete them, not that they are backed up.
# =============================================================================
volumes:
postgres-data:
external: true
name: ${DATA_VOLUME_PREFIX:-linkedin-search}-postgres-data
elasticsearch-data:
external: true
name: ${DATA_VOLUME_PREFIX:-linkedin-search}-elasticsearch-data
logstash-data:
external: true
name: ${DATA_VOLUME_PREFIX:-linkedin-search}-logstash-data