Add production Docker Compose bundle (#985)

Signed-off-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
tlongwell-block
2026-06-13 13:28:25 -04:00
committed by GitHub
co-authored by npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta
parent a32681fd48
commit 6caa359d70
7 changed files with 477 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
# Buzz production Docker Compose environment.
# Copy to .env and replace every CHANGE_ME value before running.
# The bootstrap script should generate this file for normal users.
# Image published by the public image pipeline. Use `:main` for pre-release testing. Pin `:sha-<7>` or a semver release tag for production.
BUZZ_IMAGE=ghcr.io/block/buzz:main
# Public host name. Used by compose.caddy.yml and URL-derived settings below.
BUZZ_DOMAIN=buzz.example.com
RELAY_URL=wss://buzz.example.com
BUZZ_MEDIA_BASE_URL=https://buzz.example.com/media
BUZZ_MEDIA_SERVER_DOMAIN=buzz.example.com
BUZZ_CORS_ORIGINS=https://buzz.example.com
# Production defaults. Closed relay mode requires RELAY_OWNER_PUBKEY and a stable relay key.
BUZZ_REQUIRE_AUTH_TOKEN=true
BUZZ_REQUIRE_RELAY_MEMBERSHIP=true
BUZZ_ALLOW_NIP_OA_AUTH=true
BUZZ_AUTO_MIGRATE=true
BUZZ_GIT_CONFORMANCE_PROBE=true
RUST_LOG=buzz_relay=info,buzz_db=info,buzz_auth=info,buzz_pubsub=info,tower_http=info
# Owner identity. Set to a 64-character hex Nostr pubkey.
RELAY_OWNER_PUBKEY=CHANGE_ME_OWNER_PUBKEY_HEX
# Stable secrets. Generate once, keep in .env, and back up securely.
BUZZ_RELAY_PRIVATE_KEY=CHANGE_ME_64_HEX_PRIVATE_KEY
BUZZ_GIT_HOOK_HMAC_SECRET=CHANGE_ME_RANDOM_64_HEX
POSTGRES_DB=buzz
POSTGRES_USER=buzz
POSTGRES_PASSWORD=CHANGE_ME_RANDOM_PASSWORD
REDIS_PASSWORD=CHANGE_ME_RANDOM_PASSWORD
TYPESENSE_API_KEY=CHANGE_ME_RANDOM_API_KEY
BUZZ_S3_ACCESS_KEY=CHANGE_ME_RANDOM_ACCESS_KEY
BUZZ_S3_SECRET_KEY=CHANGE_ME_RANDOM_SECRET_KEY
BUZZ_S3_BUCKET=buzz-media
# Optional host ports. Base compose publishes the relay directly on BUZZ_HTTP_PORT.
BUZZ_HTTP_PORT=3000
# Caddy host ports. Only used with compose.caddy.yml.
CADDY_HTTP_PORT=80
CADDY_HTTPS_PORT=443
# Dev override ports. Only used with compose.dev.yml.
POSTGRES_PORT=5432
REDIS_PORT=6379
TYPESENSE_PORT=8108
MINIO_API_PORT=9000
MINIO_CONSOLE_PORT=9001
ADMINER_PORT=8082
PROMETHEUS_PORT=9090
+5
View File
@@ -0,0 +1,5 @@
{$BUZZ_DOMAIN} {
encode zstd gzip
reverse_proxy relay:3000
}
+57
View File
@@ -0,0 +1,57 @@
# Buzz Docker Compose deployment
This is the single-node/VPS deployment bundle. It is intentionally separate from
the root `docker-compose.yml`, which remains local development infrastructure.
## Quick start
```bash
cd deploy/compose
cp .env.example .env
$EDITOR .env # replace every CHANGE_ME value
./run.sh start
```
For a public VPS with automatic Let's Encrypt certificates:
```bash
cd deploy/compose
BUZZ_COMPOSE_TLS=true ./run.sh start
```
The bootstrap script should eventually replace manual `.env` editing for normal
users. It is responsible for generating stable secrets and, optionally, an owner
keypair.
## Production notes
- Requires Docker Compose v2.24.4 or newer; the TLS override uses Compose's
`!reset` tag to remove the direct relay port when Caddy terminates HTTPS.
- Default `BUZZ_IMAGE` tracks `ghcr.io/block/buzz:main` for early testing. Pin it to `ghcr.io/block/buzz:sha-<7>` or a semver release tag for production once available.
- Keep `BUZZ_RELAY_PRIVATE_KEY`, `BUZZ_GIT_HOOK_HMAC_SECRET`, database/Redis,
Typesense, and S3 secrets stable across restarts.
- `RELAY_OWNER_PUBKEY` is intentionally not prefixed with `BUZZ_`; it must be a
64-character hex Nostr pubkey when closed relay mode is enabled.
- `BUZZ_AUTO_MIGRATE=true` requires an image that includes embedded SQLx
migrations. Do not share this quick start for a fresh public install until PR
#988 is merged and `ghcr.io/block/buzz:main` has been rebuilt from it. Before
then, this bundle is only suitable for instances whose database schema has
already been applied.
- The stack uses Postgres, Redis, Typesense, MinIO, and a git data volume because
those are real Buzz dependencies today. Minimal mode can simplify this later.
Run `./run.sh backup-hint` for the backup checklist.
## Validation
Before sharing an install link publicly, verify a fresh install with:
```bash
cd deploy/compose
cp .env.example .env
$EDITOR .env
./run.sh config
./run.sh start
curl -fsS "http://127.0.0.1:$(grep -E '^BUZZ_HTTP_PORT=' .env | cut -d= -f2-)/_liveness"
./run.sh status
```
+29
View File
@@ -0,0 +1,29 @@
services:
relay:
ports: !reset []
caddy:
image: caddy:2-alpine
depends_on:
relay:
condition: service_healthy
environment:
BUZZ_DOMAIN: ${BUZZ_DOMAIN:?set BUZZ_DOMAIN}
ports:
- "${CADDY_HTTP_PORT:-80}:80"
- "${CADDY_HTTPS_PORT:-443}:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- buzz-caddy-data:/data
- buzz-caddy-config:/config
restart: unless-stopped
networks:
- buzz-net
volumes:
buzz-caddy-data:
labels:
com.buzz.volume: caddy-data
buzz-caddy-config:
labels:
com.buzz.volume: caddy-config
+52
View File
@@ -0,0 +1,52 @@
services:
postgres:
ports:
- "${POSTGRES_PORT:-5432}:5432"
redis:
ports:
- "${REDIS_PORT:-6379}:6379"
typesense:
environment:
TYPESENSE_ENABLE_CORS: "true"
ports:
- "${TYPESENSE_PORT:-8108}:8108"
minio:
ports:
- "${MINIO_API_PORT:-9000}:9000"
- "${MINIO_CONSOLE_PORT:-9001}:9001"
adminer:
image: adminer:latest
container_name: buzz-adminer
depends_on:
postgres:
condition: service_healthy
environment:
ADMINER_DEFAULT_SERVER: postgres
ports:
- "${ADMINER_PORT:-8082}:8080"
restart: unless-stopped
networks:
- buzz-net
prometheus:
image: prom/prometheus:latest
container_name: buzz-prometheus
volumes:
- ../../prometheus.yml:/etc/prometheus/prometheus.yml:ro
- buzz-prometheus-data:/prometheus
ports:
- "${PROMETHEUS_PORT:-9090}:9090"
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
networks:
- buzz-net
volumes:
buzz-prometheus-data:
labels:
com.buzz.volume: prometheus
+168
View File
@@ -0,0 +1,168 @@
name: buzz-prod
services:
relay:
image: ${BUZZ_IMAGE:-ghcr.io/block/buzz:main}
env_file:
- .env
environment:
BUZZ_BIND_ADDR: 0.0.0.0:3000
BUZZ_HEALTH_PORT: "8080"
BUZZ_METRICS_PORT: "9102"
DATABASE_URL: postgres://${POSTGRES_USER:-buzz}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-buzz}
REDIS_URL: redis://:${REDIS_PASSWORD:?set REDIS_PASSWORD}@redis:6379
TYPESENSE_URL: http://typesense:8108
BUZZ_S3_ENDPOINT: http://minio:9000
BUZZ_S3_ACCESS_KEY: ${BUZZ_S3_ACCESS_KEY:?set BUZZ_S3_ACCESS_KEY}
BUZZ_S3_SECRET_KEY: ${BUZZ_S3_SECRET_KEY:?set BUZZ_S3_SECRET_KEY}
BUZZ_S3_BUCKET: ${BUZZ_S3_BUCKET:-buzz-media}
BUZZ_GIT_REPO_PATH: /data/git
BUZZ_AUTO_MIGRATE: ${BUZZ_AUTO_MIGRATE:-true}
BUZZ_GIT_CONFORMANCE_PROBE: ${BUZZ_GIT_CONFORMANCE_PROBE:-true}
ports:
- "${BUZZ_HTTP_PORT:-3000}:3000"
volumes:
- buzz-git-data:/data/git
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
typesense:
condition: service_healthy
minio:
condition: service_healthy
minio-init:
condition: service_completed_successfully
# Probe /_readiness over /dev/tcp because the runtime image has bash but no curl/wget/socat.
healthcheck:
test:
[
"CMD-SHELL",
"bash -ec 'exec 3<>/dev/tcp/127.0.0.1/8080; printf \"GET /_readiness HTTP/1.1\\r\\nHost: 127.0.0.1\\r\\nConnection: close\\r\\n\\r\\n\" >&3; grep -q \"200 OK\" <&3'",
]
interval: 10s
timeout: 3s
retries: 12
start_period: 30s
restart: unless-stopped
networks:
- buzz-net
postgres:
image: postgres:18-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB:-buzz}
POSTGRES_USER: ${POSTGRES_USER:-buzz}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- buzz-postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 12
start_period: 10s
restart: unless-stopped
networks:
- buzz-net
redis:
image: redis:8-alpine
command: ["redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD:?set REDIS_PASSWORD}"]
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD:?set REDIS_PASSWORD}
volumes:
- buzz-redis-data:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli -a \"$${REDIS_PASSWORD}\" ping | grep -q PONG"]
interval: 5s
timeout: 3s
retries: 12
start_period: 5s
restart: unless-stopped
networks:
- buzz-net
typesense:
image: typesense/typesense:30.2
environment:
TYPESENSE_DATA_DIR: /data
TYPESENSE_API_KEY: ${TYPESENSE_API_KEY:?set TYPESENSE_API_KEY}
TYPESENSE_ENABLE_CORS: "false"
volumes:
- buzz-typesense-data:/data
healthcheck:
test:
[
"CMD-SHELL",
"bash -c 'exec 3<>/dev/tcp/127.0.0.1/8108; printf \"GET /health HTTP/1.1\\r\\nHost: 127.0.0.1\\r\\nConnection: close\\r\\n\\r\\n\" >&3; cat <&3 | grep -q ok'",
]
interval: 10s
timeout: 5s
retries: 12
start_period: 15s
restart: unless-stopped
networks:
- buzz-net
minio:
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${BUZZ_S3_ACCESS_KEY:?set BUZZ_S3_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${BUZZ_S3_SECRET_KEY:?set BUZZ_S3_SECRET_KEY}
volumes:
- buzz-minio-data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:9000/minio/health/live"]
interval: 5s
timeout: 5s
retries: 12
start_period: 10s
restart: unless-stopped
networks:
- buzz-net
minio-init:
image: minio/mc:RELEASE.2025-08-13T08-35-41Z
depends_on:
minio:
condition: service_healthy
environment:
BUZZ_S3_ACCESS_KEY: ${BUZZ_S3_ACCESS_KEY:?set BUZZ_S3_ACCESS_KEY}
BUZZ_S3_SECRET_KEY: ${BUZZ_S3_SECRET_KEY:?set BUZZ_S3_SECRET_KEY}
BUZZ_S3_BUCKET: ${BUZZ_S3_BUCKET:-buzz-media}
entrypoint: >
/bin/sh -euc '
mc alias set local http://minio:9000 "$${BUZZ_S3_ACCESS_KEY}" "$${BUZZ_S3_SECRET_KEY}"
mc mb --ignore-existing "local/$${BUZZ_S3_BUCKET}"
mc anonymous set none "local/$${BUZZ_S3_BUCKET}"
'
restart: "no"
networks:
- buzz-net
volumes:
buzz-postgres-data:
labels:
com.buzz.volume: postgres
buzz-redis-data:
labels:
com.buzz.volume: redis
buzz-typesense-data:
labels:
com.buzz.volume: typesense
buzz-minio-data:
labels:
com.buzz.volume: minio
buzz-git-data:
labels:
com.buzz.volume: git
networks:
buzz-net:
driver: bridge
labels:
com.buzz.network: production
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${SCRIPT_DIR}"
COMPOSE_FILES=(-f compose.yml)
if [[ "${BUZZ_COMPOSE_TLS:-false}" == "true" ]]; then
COMPOSE_FILES+=(-f compose.caddy.yml)
fi
if [[ "${BUZZ_COMPOSE_DEV:-false}" == "true" ]]; then
COMPOSE_FILES+=(-f compose.dev.yml)
fi
compose() {
docker compose --env-file .env "${COMPOSE_FILES[@]}" "$@"
}
require_env() {
if [[ ! -f .env ]]; then
cat >&2 <<'MSG'
Missing deploy/compose/.env.
Copy .env.example to .env and replace every CHANGE_ME value, or run the bootstrap
script once it lands. Do not start production with generated secrets missing.
MSG
exit 1
fi
if grep -Eq '^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*=.*CHANGE_ME' .env; then
cat >&2 <<'MSG'
deploy/compose/.env still contains CHANGE_ME placeholders.
Generate stable secrets first; these values must not rotate on restart.
MSG
exit 1
fi
}
backup_hint() {
cat <<'MSG'
Back up these before upgrades and on a regular schedule:
- deploy/compose/.env, especially BUZZ_RELAY_PRIVATE_KEY, DB/Redis/Typesense/S3 secrets, and BUZZ_GIT_HOOK_HMAC_SECRET
- The owner private key if bootstrap generated one for RELAY_OWNER_PUBKEY
- Postgres data (prefer pg_dump or a quiesced volume snapshot)
- MinIO/S3 bucket contents for media and git objects
- buzz-git-data volume (BUZZ_GIT_REPO_PATH=/data/git)
- Caddy data/config volumes if using compose.caddy.yml
Keep Postgres + object/git state snapshots from the same maintenance window.
MSG
}
case "${1:-help}" in
start|up)
require_env
compose up -d --wait
;;
stop|down)
compose down
;;
restart)
require_env
compose up -d --wait --force-recreate relay
;;
pull)
require_env
compose pull
;;
upgrade)
require_env
compose pull
compose up -d --wait
backup_hint
;;
logs)
shift || true
compose logs -f "${@:-relay}"
;;
status|ps)
compose ps
;;
config)
require_env
compose config
;;
backup-hint)
backup_hint
;;
help|-h|--help)
cat <<'MSG'
Usage: ./run.sh <command>
Commands:
start Start Buzz with docker compose up -d --wait
stop Stop containers without deleting volumes
restart Recreate the relay after env/image changes
pull Pull configured images
upgrade Pull and restart, then print backup reminders
logs [svc] Follow logs (default: relay)
status Show compose service status
config Render merged compose config
backup-hint Print the production backup checklist
Environment switches:
BUZZ_COMPOSE_TLS=true Include compose.caddy.yml for automatic HTTPS
BUZZ_COMPOSE_DEV=true Include compose.dev.yml for local admin ports/tools
MSG
;;
*)
echo "Unknown command: $1" >&2
echo "Run ./run.sh help" >&2
exit 1
;;
esac