Skip to content

Commit df94601

Browse files
authored
feat: add demo deployment docker-compose with Caddy and PostgreSQL (#1158)
* feat: add demo deployment docker-compose with Caddy and PostgreSQL Adds the production docker-compose stack for the demo DigitalOcean Droplet: - deploy/demo/docker-compose.yml: three-service stack (postgres:16-alpine, meridian unified binary, caddy:2-alpine) with health checks and internal networking; only ports 80/443 exposed to host via Caddy - deploy/demo/Caddyfile: terminates TLS with Cloudflare Origin Certificate and reverse-proxies to meridian:8090 - deploy/demo/.env.demo.example: extended with POSTGRES_USER/PASSWORD/DB variables consumed by docker-compose interpolation - .gitignore: exclude deploy/demo/.env.demo and deploy/demo/certs/*.pem * fix: address review feedback on demo docker-compose - Remove meridian CMD-SHELL healthcheck: image is gcr.io/distroless/static-debian12 which has no shell or wget; caddy now depends on service_started and handles upstream retries itself - Use :? required substitution for POSTGRES_PASSWORD to fail fast at docker compose up when the variable is unset - Document that POSTGRES_PASSWORD must be URL-safe (alphanumeric + - _ . ~) to avoid corrupting the connection string interpolation --------- Co-authored-by: Ben Coombs <bjcoombs@users.noreply.github.com>
1 parent 3f5b067 commit df94601

4 files changed

Lines changed: 131 additions & 9 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,7 @@ deployments/k8s/base/secret.yaml
6969

7070
# IntelliJ HTTP Client private environment (may contain secrets)
7171
http/http-client.private.env.json
72+
73+
# Demo deployment secrets
74+
deploy/demo/.env.demo
75+
deploy/demo/certs/*.pem

deploy/demo/.env.demo.example

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,28 @@
1818
MERIDIAN_IMAGE=ghcr.io/meridianhub/meridian:demo
1919

2020
# ---------------------------------------------------------------------------
21-
# Database
21+
# Database — PostgreSQL container (docker-compose)
2222
# ---------------------------------------------------------------------------
2323

24-
# [OPTIONAL] Database driver: "cockroachdb" (default) or "postgres".
25-
# Set to "postgres" when using a PostgreSQL 13+ database (e.g., DigitalOcean Managed PostgreSQL).
26-
# When DB_DRIVER=postgres, DATABASE_URL default port changes from 26257 to 5432.
27-
#DB_DRIVER=cockroachdb
24+
# [REQUIRED] PostgreSQL superuser password for the bundled postgres container.
25+
# Used by both the postgres service and the meridian service connection strings.
26+
# IMPORTANT: Use only alphanumeric characters and the symbols - _ . ~
27+
# URL-reserved characters (@, :, /, #, ?, %, +) will corrupt the connection string.
28+
POSTGRES_PASSWORD=changeme
2829

29-
# [REQUIRED] Database connection string used by the application.
30-
# CockroachDB (default): postgres://root@cockroachdb:26257/defaultdb?sslmode=disable
31-
# PostgreSQL (DigitalOcean Managed): postgres://doadmin:<password>@<host>:25060/defaultdb?sslmode=require
32-
DATABASE_URL=postgres://root@cockroachdb:26257/defaultdb?sslmode=disable
30+
# [OPTIONAL] PostgreSQL username (default: meridian).
31+
POSTGRES_USER=meridian
32+
33+
# [OPTIONAL] PostgreSQL database name (default: meridian).
34+
POSTGRES_DB=meridian
35+
36+
# [OPTIONAL] Database driver — always "postgres" for the demo stack.
37+
DB_DRIVER=postgres
38+
39+
# DATABASE_URL and per-service URLs are constructed automatically by docker-compose
40+
# from POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB above.
41+
# Override here only if pointing at an external database instead of the bundled container.
42+
#DATABASE_URL=postgres://meridian:<password>@postgres:5432/meridian?sslmode=disable
3343

3444
# [OPTIONAL] GORM database connection pool settings.
3545
DB_CONN_MAX_IDLE_TIME=10m

deploy/demo/Caddyfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
demo.meridianhub.cloud, *.demo.meridianhub.cloud {
2+
tls /etc/caddy/certs/origin-cert.pem /etc/caddy/certs/origin-key.pem
3+
reverse_proxy meridian:8090
4+
}

deploy/demo/docker-compose.yml

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Meridian demo deployment stack
2+
#
3+
# Services:
4+
# postgres - PostgreSQL 16 (internal only, not exposed to host)
5+
# meridian - Unified binary; starts after postgres is healthy
6+
# caddy - Reverse proxy; exposes 80/443, terminates TLS via Cloudflare Origin Certificate
7+
#
8+
# Usage:
9+
# cp deploy/demo/.env.demo.example /opt/meridian/.env
10+
# # Edit /opt/meridian/.env with actual values
11+
# docker compose --env-file /opt/meridian/.env -f /opt/meridian/docker-compose.yml up -d
12+
#
13+
# Directory layout expected on the host:
14+
# /opt/meridian/.env - Filled-in environment file
15+
# /opt/meridian/Caddyfile - Caddy configuration
16+
# /opt/meridian/certs/ - Cloudflare Origin Certificate files
17+
# origin-cert.pem
18+
# origin-key.pem
19+
20+
services:
21+
postgres:
22+
image: postgres:16-alpine
23+
restart: unless-stopped
24+
environment:
25+
POSTGRES_USER: ${POSTGRES_USER:-meridian}
26+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in the env file}
27+
POSTGRES_DB: ${POSTGRES_DB:-meridian}
28+
volumes:
29+
- postgres_data:/var/lib/postgresql/data
30+
healthcheck:
31+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-meridian} -d ${POSTGRES_DB:-meridian}"]
32+
interval: 5s
33+
timeout: 5s
34+
retries: 10
35+
start_period: 10s
36+
# Not exposed to host — accessed only by meridian on the internal network
37+
38+
meridian:
39+
image: ${MERIDIAN_IMAGE:-ghcr.io/meridianhub/meridian:demo}
40+
restart: unless-stopped
41+
depends_on:
42+
postgres:
43+
condition: service_healthy
44+
environment:
45+
# --- Application ---
46+
ENVIRONMENT: ${ENVIRONMENT:-demo}
47+
LOG_LEVEL: ${LOG_LEVEL:-info}
48+
49+
# --- Database driver ---
50+
DB_DRIVER: postgres
51+
52+
# --- Database URLs (all services share the same PostgreSQL instance in demo) ---
53+
DATABASE_URL: postgres://${POSTGRES_USER:-meridian}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in the env file}@postgres:5432/${POSTGRES_DB:-meridian}?sslmode=disable
54+
PLATFORM_DATABASE_URL: postgres://${POSTGRES_USER:-meridian}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in the env file}@postgres:5432/${POSTGRES_DB:-meridian}?sslmode=disable
55+
CURRENT_ACCOUNT_DATABASE_URL: postgres://${POSTGRES_USER:-meridian}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in the env file}@postgres:5432/${POSTGRES_DB:-meridian}?sslmode=disable
56+
FINANCIAL_ACCOUNTING_DATABASE_URL: postgres://${POSTGRES_USER:-meridian}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in the env file}@postgres:5432/${POSTGRES_DB:-meridian}?sslmode=disable
57+
POSITION_KEEPING_DATABASE_URL: postgres://${POSTGRES_USER:-meridian}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in the env file}@postgres:5432/${POSTGRES_DB:-meridian}?sslmode=disable
58+
PAYMENT_ORDER_DATABASE_URL: postgres://${POSTGRES_USER:-meridian}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in the env file}@postgres:5432/${POSTGRES_DB:-meridian}?sslmode=disable
59+
PARTY_DATABASE_URL: postgres://${POSTGRES_USER:-meridian}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in the env file}@postgres:5432/${POSTGRES_DB:-meridian}?sslmode=disable
60+
INTERNAL_BANK_ACCOUNT_DATABASE_URL: postgres://${POSTGRES_USER:-meridian}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in the env file}@postgres:5432/${POSTGRES_DB:-meridian}?sslmode=disable
61+
MARKET_INFORMATION_DATABASE_URL: postgres://${POSTGRES_USER:-meridian}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in the env file}@postgres:5432/${POSTGRES_DB:-meridian}?sslmode=disable
62+
REFERENCE_DATA_DATABASE_URL: postgres://${POSTGRES_USER:-meridian}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in the env file}@postgres:5432/${POSTGRES_DB:-meridian}?sslmode=disable
63+
64+
# --- Authentication ---
65+
AUTH_MODE: ${AUTH_MODE:-disabled}
66+
67+
# --- Feature flags ---
68+
BILLING_ENABLED: ${BILLING_ENABLED:-false}
69+
REDIS_ENABLED: ${REDIS_ENABLED:-false}
70+
KAFKA_ENABLED: ${KAFKA_ENABLED:-false}
71+
72+
# --- Multi-tenancy ---
73+
MASTER_TENANT_ID: ${MASTER_TENANT_ID:-meridian_master}
74+
75+
# --- Connection pool ---
76+
DB_MAX_OPEN_CONNS: ${DB_MAX_OPEN_CONNS:-25}
77+
DB_MAX_IDLE_CONNS: ${DB_MAX_IDLE_CONNS:-5}
78+
DB_CONN_MAX_LIFETIME: ${DB_CONN_MAX_LIFETIME:-5m}
79+
DB_CONN_MAX_IDLE_TIME: ${DB_CONN_MAX_IDLE_TIME:-10m}
80+
# Note: The meridian image uses gcr.io/distroless/static-debian12 (no shell,
81+
# no wget), so a CMD-SHELL healthcheck cannot run. Health is monitored via
82+
# the gRPC health check service on port 50051. Caddy uses service_started
83+
# and retries upstream connections itself during meridian startup.
84+
# Not exposed to host — accessed only by caddy on the internal network
85+
86+
caddy:
87+
image: caddy:2-alpine
88+
restart: unless-stopped
89+
depends_on:
90+
meridian:
91+
condition: service_started
92+
ports:
93+
- "80:80"
94+
- "443:443"
95+
volumes:
96+
- /opt/meridian/Caddyfile:/etc/caddy/Caddyfile:ro
97+
- /opt/meridian/certs:/etc/caddy/certs:ro
98+
- caddy_data:/data
99+
- caddy_config:/config
100+
101+
volumes:
102+
postgres_data:
103+
caddy_data:
104+
caddy_config:

0 commit comments

Comments
 (0)