- multi-stage Dockerfile; worker bundled to build/worker.mjs (no tsx at runtime) - docker-compose.yml + docker-compose.nginx.yml (127.0.0.1:3320), Nginx site, encrypted backups - production URLs (datewise.metatronlabz.com) in shopify.app.toml; URL auto-update off - verified locally: healthz/landing/privacy/terms 200, unsigned proxy/webhook 400, worker starts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
73 lines
2.4 KiB
YAML
73 lines
2.4 KiB
YAML
# Production stack for one VPS: Postgres + the app + the worker + Caddy (automatic HTTPS from Let's Encrypt).
|
|
# Usage: cp .env.example .env (fill in the production section) then docker compose up -d --build
|
|
# If the VPS already runs Nginx on 80/443, use docker-compose.nginx.yml instead of Caddy (see that file).
|
|
x-app-env: &app-env
|
|
NODE_ENV: production
|
|
PORT: 3000
|
|
DATABASE_URL: postgresql://datewise:${POSTGRES_PASSWORD}@db:5432/datewise?schema=public
|
|
SHOPIFY_API_KEY: ${SHOPIFY_API_KEY:?set SHOPIFY_API_KEY in .env}
|
|
SHOPIFY_API_SECRET: ${SHOPIFY_API_SECRET:?set SHOPIFY_API_SECRET in .env}
|
|
SHOPIFY_APP_URL: https://${APP_DOMAIN:?set APP_DOMAIN in .env}
|
|
SCOPES: read_products,write_products,write_publications,read_orders,write_orders,read_customers,read_locations,read_inventory,write_validations,write_delivery_customizations,write_payment_customizations
|
|
APP_ENCRYPTION_KEY: ${APP_ENCRYPTION_KEY:?set APP_ENCRYPTION_KEY in .env (openssl rand -base64 32)}
|
|
GOOGLE_MAPS_API_KEY: ${GOOGLE_MAPS_API_KEY:-}
|
|
RESEND_API_KEY: ${RESEND_API_KEY:-}
|
|
EMAIL_FROM_DOMAIN: ${EMAIL_FROM_DOMAIN:-}
|
|
|
|
services:
|
|
db:
|
|
image: postgres:17-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_DB: datewise
|
|
POSTGRES_USER: datewise
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
|
|
volumes:
|
|
- pgdata:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U datewise -d datewise"]
|
|
interval: 5s
|
|
retries: 10
|
|
|
|
app:
|
|
build: .
|
|
image: datewise:latest
|
|
restart: unless-stopped
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
environment: *app-env
|
|
expose:
|
|
- "3000"
|
|
|
|
# Sweeps expired holds, sends reminders, offers freed places to the waitlist, reconciles missed orders.
|
|
worker:
|
|
image: datewise:latest
|
|
restart: unless-stopped
|
|
depends_on:
|
|
app:
|
|
condition: service_healthy # the app has applied migrations
|
|
environment: *app-env
|
|
command: ["node", "build/worker.mjs"]
|
|
healthcheck:
|
|
disable: true
|
|
|
|
caddy:
|
|
image: caddy:2-alpine
|
|
restart: unless-stopped
|
|
depends_on: [app]
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
environment:
|
|
APP_DOMAIN: ${APP_DOMAIN}
|
|
volumes:
|
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
- caddy_data:/data
|
|
- caddy_config:/config
|
|
|
|
volumes:
|
|
pgdata:
|
|
caddy_data:
|
|
caddy_config:
|