PostHog loads after the page is idle through a same-origin /ingest proxy and follows the signed-in Convex user. Plausible uses the per-site script through a /pa proxy. Sentry keeps errors and traces only. Pages load only the @spoon/ui modules they import, and client code reads public env from publicEnv.ts so zod stays out of the browser. Both analytics integrations stay off until their env is set in Infisical prod.
170 lines
7.8 KiB
YAML
170 lines
7.8 KiB
YAML
# Production Compose for Spoon
|
|
# -----------------------------------------------------------------------------
|
|
# Reference deployment for the production host. Copy this to the server and run
|
|
# with `docker compose -f compose.prod.yml up -d` (alongside your prod `.env`).
|
|
#
|
|
# Two things in here are load-bearing for the agent ("run a thread") to work.
|
|
# If you change them, read the comments first:
|
|
#
|
|
# 1. AGENT WORKDIR (spoon-agent-worker): the worker is containerized but
|
|
# creates and `exec`s into the per-user box (spoon-box-{username}) by talking
|
|
# to the HOST Docker daemon. The host can only bind-mount real HOST paths, so
|
|
# the work directory MUST be a bind mount whose path is IDENTICAL inside and
|
|
# outside the container, and SPOON_AGENT_HOST_WORKDIR must match it. A named
|
|
# volume does NOT work here because its real host path is hidden from the
|
|
# worker. All three references to /var/lib/spoon-agent/work below must stay
|
|
# in sync; change them together if you want the data somewhere else.
|
|
#
|
|
# 2. IMAGE FRESHNESS: services use `pull_policy: always` + Watchtower labels so
|
|
# a redeploy / new push always lands. The *box* image is pulled by the worker
|
|
# itself on startup (see SPOON_AGENT_JOB_IMAGE); restarting the worker (which
|
|
# Watchtower does on a new image) re-pulls a fresh box image.
|
|
|
|
networks:
|
|
nginx-bridge: # Change to network you plan to use
|
|
external: true
|
|
|
|
services:
|
|
spoon-next:
|
|
image: git.gbrown.org/gib/${NEXT_CONTAINER_NAME}:latest
|
|
container_name: ${NEXT_CONTAINER_NAME}
|
|
hostname: ${NEXT_CONTAINER_NAME}
|
|
domainname: ${NEXT_DOMAIN}
|
|
networks: ['${NETWORK:-nginx-bridge}']
|
|
#ports: ['${NEXT_PORT}:${NEXT_PORT}']
|
|
pull_policy: always
|
|
environment:
|
|
- NODE_ENV=${NODE_ENV}
|
|
- SENTRY_AUTH_TOKEN=${SENTRY_AUTH_TOKEN}
|
|
- NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL:-http://localhost:${NEXT_PORT:-3000}}
|
|
- NEXT_PUBLIC_CONVEX_URL=${NEXT_PUBLIC_CONVEX_URL:-http://${BACKEND_CONTAINER_NAME:-spoon-backend}:${BACKEND_PORT:-3210}}
|
|
- NEXT_PUBLIC_PLAUSIBLE_SRC=${NEXT_PUBLIC_PLAUSIBLE_SRC:-}
|
|
- NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY:-}
|
|
- NEXT_PUBLIC_POSTHOG_HOST=${NEXT_PUBLIC_POSTHOG_HOST:-}
|
|
- NEXT_PUBLIC_SENTRY_DSN=${NEXT_PUBLIC_SENTRY_DSN}
|
|
- NEXT_PUBLIC_SENTRY_URL=${NEXT_PUBLIC_SENTRY_URL}
|
|
- NEXT_PUBLIC_SENTRY_ORG=${NEXT_PUBLIC_SENTRY_ORG:-sentry}
|
|
- NEXT_PUBLIC_SENTRY_PROJECT_NAME=${NEXT_PUBLIC_SENTRY_PROJECT_NAME}
|
|
- SPOON_AGENT_WORKER_URL=${SPOON_AGENT_WORKER_URL:-http://spoon-agent-worker:3921}
|
|
- SPOON_AGENT_WORKER_INTERNAL_TOKEN=${SPOON_AGENT_WORKER_INTERNAL_TOKEN}
|
|
- SPOON_WORKER_TOKEN=${SPOON_WORKER_TOKEN}
|
|
# NOTE: the Terminal tab needs NEXT_PUBLIC_SPOON_AGENT_WORKER_WS_URL, which is
|
|
# a NEXT_PUBLIC (build-time) var — it must be baked into the spoon-next image
|
|
# at build (via the build env file / CI), NOT set as a runtime env here. Also
|
|
# requires nginx to proxy worker.<domain> → spoon-agent-worker:3921 with WS
|
|
# upgrade. See docs/agent-terminal.md.
|
|
depends_on: ['spoon-backend', 'spoon-postgres']
|
|
labels: ['com.centurylinklabs.watchtower.enable=true']
|
|
tty: true
|
|
stdin_open: true
|
|
restart: unless-stopped
|
|
|
|
spoon-agent-worker:
|
|
image: git.gbrown.org/gib/spoon-agent-worker:latest
|
|
container_name: spoon-agent-worker
|
|
hostname: spoon-agent-worker
|
|
domainname: worker.${NEXT_DOMAIN:-spoon.gbrown.org}
|
|
networks: ['${NETWORK:-nginx-bridge}']
|
|
pull_policy: always
|
|
environment:
|
|
- GITHUB_APP_ID=${GITHUB_APP_ID}
|
|
- GITHUB_APP_PRIVATE_KEY=${GITHUB_APP_PRIVATE_KEY}
|
|
- NEXT_PUBLIC_CONVEX_URL=https://api.spoon.gbrown.org
|
|
- SPOON_AGENT_WORKER_ID=${SPOON_AGENT_WORKER_ID:-production-worker}
|
|
- SPOON_AGENT_JOB_IMAGE=${SPOON_AGENT_JOB_IMAGE:-git.gbrown.org/gib/spoon-agent-job:latest}
|
|
- SPOON_AGENT_RUNTIME=docker
|
|
- SPOON_AGENT_NETWORK=${NETWORK:-nginx-bridge}
|
|
# The work directory MUST be the same absolute path here, in the bind mount
|
|
# below, and in SPOON_AGENT_HOST_WORKDIR. See header note (1).
|
|
- SPOON_AGENT_WORKDIR=/var/lib/spoon-agent/work
|
|
- SPOON_AGENT_HOST_WORKDIR=/var/lib/spoon-agent/work
|
|
- SPOON_AGENT_WORKER_HTTP_PORT=${SPOON_AGENT_WORKER_HTTP_PORT:-3921}
|
|
- SPOON_AGENT_WORKER_INTERNAL_TOKEN=${SPOON_AGENT_WORKER_INTERNAL_TOKEN}
|
|
- SPOON_AGENT_MAX_CONCURRENT_JOBS=${SPOON_AGENT_MAX_CONCURRENT_JOBS:-1}
|
|
- SPOON_AGENT_JOB_TIMEOUT_MS=${SPOON_AGENT_JOB_TIMEOUT_MS:-1800000}
|
|
- SPOON_WORKER_TOKEN=${SPOON_WORKER_TOKEN}
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
# Identical host:container path so the sibling per-user box can bind-mount
|
|
# the persistent home (homes/{username}) via the host daemon. Do NOT switch
|
|
# this to a named volume. See header note (1).
|
|
- /var/lib/spoon-agent/work:/var/lib/spoon-agent/work
|
|
labels: ['com.centurylinklabs.watchtower.enable=true']
|
|
tty: true
|
|
stdin_open: true
|
|
restart: unless-stopped
|
|
|
|
spoon-backend:
|
|
image: ghcr.io/get-convex/convex-backend:${BACKEND_TAG:-latest}
|
|
container_name: ${BACKEND_CONTAINER_NAME:-spoon-backend}
|
|
hostname: ${BACKEND_CONTAINER_NAME:-spoon-backend}
|
|
domainname: ${BACKEND_DOMAIN:-convex.spoon.gbrown.org}
|
|
networks: ['${NETWORK:-nginx-bridge}']
|
|
#user: '1000:1000'
|
|
#ports: ['${BACKEND_PORT:-3210}:3210','${SITE_PROXY_PORT:-3211}:3211']
|
|
volumes: [./volumes/convex:/convex/data]
|
|
pull_policy: always
|
|
environment:
|
|
- INSTANCE_NAME=${INSTANCE_NAME}
|
|
- CONVEX_CLOUD_ORIGIN=${CONVEX_CLOUD_ORIGIN:-http://${BACKEND_CONTAINER_NAME:-spoon-backend}:${BACKEND_PORT:-3210}}
|
|
- CONVEX_SITE_ORIGIN=${CONVEX_SITE_ORIGIN:-http://${BACKEND_CONTAINER_NAME:-spoon-backend}:${SITE_PROXY_PORT:-3211}}
|
|
- DISABLE_BEACON=${DISABLE_BEACON:-true}
|
|
- REDACT_LOGS_TO_CLIENT=${REDACT_LOGS_TO_CLIENT:-true}
|
|
- DO_NOT_REQUIRE_SSL=${DO_NOT_REQUIRE_SSL:-false}
|
|
- POSTGRES_URL=${POSTGRES_URL}
|
|
depends_on: ['spoon-postgres']
|
|
labels: ['com.centurylinklabs.watchtower.enable=true']
|
|
stdin_open: true
|
|
tty: true
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: curl -f http://localhost:3210/version
|
|
interval: 5s
|
|
start_period: 10s
|
|
stop_grace_period: 10s
|
|
stop_signal: SIGINT
|
|
|
|
spoon-dashboard:
|
|
image: ghcr.io/get-convex/convex-dashboard:${DASHBOARD_TAG:-latest}
|
|
container_name: ${DASHBOARD_CONTAINER_NAME:-spoon-dashboard}
|
|
hostname: ${DASHBOARD_CONTAINER_NAME:-spoon-dashboard}
|
|
domainname: ${DASHBOARD_DOMAIN:-dashboard.${BACKEND_DOMAIN:-spoon.gbrown.org}}
|
|
networks: ['${NETWORK:-nginx-bridge}']
|
|
#user: 1000:1000
|
|
#ports: ['${DASHBOARD_PORT:-6791}:6791']
|
|
pull_policy: always
|
|
environment:
|
|
- NEXT_PUBLIC_DEPLOYMENT_URL=${NEXT_PUBLIC_DEPLOYMENT_URL:-http://${BACKEND_CONTAINER_NAME:-spoon-backend}:${PORT:-3210}}
|
|
depends_on:
|
|
spoon-backend:
|
|
condition: service_healthy
|
|
labels: ['com.centurylinklabs.watchtower.enable=true']
|
|
stdin_open: true
|
|
tty: true
|
|
restart: unless-stopped
|
|
stop_grace_period: 10s
|
|
stop_signal: SIGINT
|
|
|
|
spoon-postgres:
|
|
image: postgres:17
|
|
container_name: ${POSTGRES_CONTAINER_NAME:-spoon-postgres}
|
|
hostname: ${POSTGRES_CONTAINER_NAME:-spoon-postgres}
|
|
domainname: postgres.${NEXT_DOMAIN:-spoon.gbrown.org}
|
|
networks: ['${NETWORK:-nginx-bridge}']
|
|
# ports: ["5434:5432"]
|
|
environment:
|
|
- POSTGRES_USER=${POSTGRES_USER:-spoon}
|
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
|
- POSTGRES_DB=${POSTGRES_DB:-spoon_convex}
|
|
volumes: ['./volumes/postgres:/var/lib/postgresql/data']
|
|
labels: ['com.centurylinklabs.watchtower.enable=true']
|
|
tty: true
|
|
stdin_open: true
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ['CMD-SHELL', 'pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}']
|
|
start_period: 20s
|
|
interval: 30s
|
|
retries: 5
|
|
timeout: 5s
|