Repository navigation
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
141 lines (135 loc) · 6.17 KB
/
Copy pathdocker-compose.yml
File metadata and controls
141 lines (135 loc) · 6.17 KB
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# ── UKIP — Universal Knowledge Intelligence Platform ─────────────────────────
# Local / self-host compose, built from source. Production runs from
# docker-compose.prod.yml, which is standalone and pulls published images.
#
# Quick start:
# cp .env.example .env # fill in JWT_SECRET_KEY and other secrets
# docker compose up -d
# docker compose logs -f ukip-backend
#
# Access:
# Backend API → http://localhost:8000
# API docs → http://localhost:8000/docs
# Frontend → http://localhost:3004
#
# restart: "no" is deliberate here. With unless-stopped, starting Docker Desktop
# silently resurrected all four services — you got the whole stack when you only
# wanted the daemon, which is expensive on a small host. It also turns a crash
# into a restart loop that hides the first error (see the CRLF entrypoint bug).
# Self-hosters who want supervision should set unless-stopped explicitly or use
# the prod compose.
services:
# ── PostgreSQL 16 ───────────────────────────────────────────────────────────
postgres:
image: postgres:16-alpine
restart: "no"
environment:
POSTGRES_DB: ${POSTGRES_DB:-ukip}
POSTGRES_USER: ${POSTGRES_USER:-ukip}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-ukip_secret}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
# TCP (-h 127.0.0.1), not the Unix socket: on first init the image runs a
# temporary socket-only server (listen_addresses='') that a socket probe
# reports healthy, so dependants start before Postgres listens on TCP.
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U ${POSTGRES_USER:-ukip} -d ${POSTGRES_DB:-ukip}"]
interval: 10s
timeout: 5s
retries: 5
# ── UKIP Backend (FastAPI + Uvicorn) ────────────────────────────────────────
ukip-backend:
build:
context: .
dockerfile: Dockerfile.backend
restart: "no"
depends_on:
postgres:
condition: service_healthy
ukip-engine:
condition: service_started
environment:
DATABASE_URL: ${DOCKER_DATABASE_URL:-}
POSTGRES_HOST: ${DOCKER_POSTGRES_HOST:-postgres}
POSTGRES_PORT: ${DOCKER_POSTGRES_PORT:-5432}
POSTGRES_DB: ${DOCKER_POSTGRES_DB:-ukip}
POSTGRES_USER: ${DOCKER_POSTGRES_USER:-ukip}
POSTGRES_PASSWORD: ${DOCKER_POSTGRES_PASSWORD:-ukip_secret}
DB_POOL_SIZE: ${DB_POOL_SIZE:-20}
DB_MAX_OVERFLOW: ${DB_MAX_OVERFLOW:-10}
ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
ADMIN_PASSWORD: ${ADMIN_PASSWORD:?Set ADMIN_PASSWORD in .env}
ADMIN_PASSWORD_HASH: ${ADMIN_PASSWORD_HASH:-}
JWT_SECRET_KEY: ${JWT_SECRET_KEY:?Set JWT_SECRET_KEY in .env}
ENCRYPTION_KEY: ${ENCRYPTION_KEY:-}
ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-http://localhost:3004}
# Both are read by the embed snippet builder (_resolve_bases in
# routers/widgets.py). Without them app_base falls back to the API origin,
# so the iframe snippet targets :8000 — the backend — instead of the app.
FRONTEND_URL: ${FRONTEND_URL:-http://localhost:3004}
UKIP_PUBLIC_API_URL: ${UKIP_PUBLIC_API_URL:-http://localhost:8000}
SESSION_SECRET_KEY: ${SESSION_SECRET_KEY:-changeit}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
ENGINE_GRPC_URL: ${ENGINE_GRPC_URL:-ukip-engine:50051}
ENGINE_AUTH_TOKEN: ${ENGINE_AUTH_TOKEN:-dev-secret}
ENGINE_SYNC_THRESHOLD: ${ENGINE_SYNC_THRESHOLD:-500}
ENGINE_SHADOW_MODE: ${ENGINE_SHADOW_MODE:-false}
ENGINE_FALLBACK_PYTHON: ${ENGINE_FALLBACK_PYTHON:-true}
ports:
- "8000:8000"
volumes:
- static_files:/app/static
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
# ── UKIP Engine (Rust gRPC) ─────────────────────────────────────────────────
ukip-engine:
build:
context: ./engine
dockerfile: Dockerfile
restart: "no"
depends_on:
postgres:
condition: service_healthy
environment:
ENGINE_DATABASE_URL: postgresql://${DOCKER_POSTGRES_USER:-ukip}:${DOCKER_POSTGRES_PASSWORD:-ukip_secret}@postgres:5432/${DOCKER_POSTGRES_DB:-ukip}
ENGINE_GRPC_PORT: ${ENGINE_GRPC_PORT:-50051}
ENGINE_LOG_LEVEL: ${ENGINE_LOG_LEVEL:-info}
ENGINE_AUTH_TOKEN: ${ENGINE_AUTH_TOKEN:-dev-secret}
ENGINE_MAX_CONCURRENT_JOBS: ${ENGINE_MAX_CONCURRENT_JOBS:-4}
# Port 50051 is NOT exposed to the host — only accessible within Docker network
healthcheck:
test: ["CMD", "/app/engine", "--health-check"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
# ── UKIP Frontend (Next.js) ─────────────────────────────────────────────────
ukip-frontend:
build:
context: ./frontend
dockerfile: Dockerfile
args:
NEXT_PUBLIC_API_URL: ${DOCKER_FRONTEND_API_URL:-http://ukip-backend:8000}
restart: "no"
depends_on:
- ukip-backend
environment:
NEXT_PUBLIC_API_URL: ${DOCKER_FRONTEND_API_URL:-http://ukip-backend:8000}
# Server-side base for middleware.ts, which fetches the widget config to
# build frame-ancestors. Distinct from NEXT_PUBLIC_API_URL because that one
# must be reachable from the *browser* while this one is resolved inside the
# container. Falling back to the browser-facing value makes the middleware
# fail closed to frame-ancestors 'none' whenever the two differ, which is
# every local setup where the browser talks to a published port.
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL:-http://ukip-backend:8000}
ports:
- "3004:3004"
volumes:
postgres_data:
static_files: