File size: 2,360 Bytes
d6f27e0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# Production stack for a single DigitalOcean Droplet. The database is Neon (DATABASE_URL in .env.prod).
#   cd /opt/notestack/deploy && docker compose --env-file .env.prod -f docker-compose.prod.yml up -d --build
# The API listens on 127.0.0.1:API_PORT (default 8010) for the reverse proxy already on this host (Nginx,
# Caddy...). On a Droplet with nothing else on ports 80/443, start the bundled Caddy with --profile caddy.

services:
  api:
    build: ../backend
    restart: unless-stopped
    env_file: .env.prod
    # Migrations are NOT applied on start. Run them by hand: docker compose --env-file .env.prod -f docker-compose.prod.yml run --rm api alembic upgrade head
    # Proxy headers let the API see the real client IP and https scheme behind Caddy.
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --proxy-headers --forwarded-allow-ips='*'
    ports:
      - "127.0.0.1:${API_PORT:-8010}:8000" # local only: the host proxy forwards API_DOMAIN here
    volumes:
      - appdata:/data
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/health')"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 40s

  worker:
    build: ../backend
    restart: unless-stopped
    env_file: .env.prod
    command: python -m app.worker
    volumes:
      - appdata:/data # same volume as the API, so both see the same stored files and corpus cache
    depends_on:
      api:
        condition: service_healthy # the API has applied migrations by the time it is healthy

  renderer:
    build: ../renderer
    restart: unless-stopped
    environment:
      INTERNAL_TOKEN: ${INTERNAL_TOKEN} # must match the API's
    profiles: ["render"] # optional: start with --profile render (needs about 2 GB of free RAM)

  caddy: # only on a Droplet with no other web server: --profile caddy
    image: caddy:2
    restart: unless-stopped
    profiles: ["caddy"]
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    environment: # only what the Caddyfile needs, not the app secrets
      API_DOMAIN: ${API_DOMAIN}
      ACME_EMAIL: ${ACME_EMAIL}
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - api

volumes:
  appdata: {}
  caddy_data: {}
  caddy_config: {}