Render the widget on the contact, event registration and both application forms, and verify the token with Siteverify in each action before anything is written to the CMS. Verification fails closed. The check is off unless both TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY are set. Allow challenges.cloudflare.com in script-src and frame-src. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
146 lines
6.5 KiB
YAML
146 lines
6.5 KiB
YAML
# ============================================================================
|
|
# SHARP-ER - full stack for local development, review and small deployments
|
|
# ----------------------------------------------------------------------------
|
|
# cp .env.example .env
|
|
# ./scripts/generate-secrets.sh --write .env
|
|
# docker compose up -d --build
|
|
#
|
|
# web http://localhost:3000 (redirects / to /en or /ar)
|
|
# cms http://localhost:1337/admin (create the first admin user here)
|
|
# db 127.0.0.1:5433 (5433 on purpose - see .env.example)
|
|
#
|
|
# Every ${VARIABLE} below is read from the .env file next to this one. The
|
|
# secrets use ${VAR:?...}, so a missing value stops the stack immediately with
|
|
# a readable message instead of a Strapi crash loop.
|
|
#
|
|
# Full walkthrough, including teardown and the CMS media caveat: README.md.
|
|
# ============================================================================
|
|
|
|
name: sharper
|
|
|
|
services:
|
|
# --------------------------------------------------------------------------
|
|
# PostgreSQL 16 - the CMS database. Data lives in a named volume, so
|
|
# `docker compose down` keeps it and only `down -v` deletes it.
|
|
# --------------------------------------------------------------------------
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_DB: ${DATABASE_NAME:-sharper_cms}
|
|
POSTGRES_USER: ${DATABASE_USERNAME:-postgres}
|
|
POSTGRES_PASSWORD: ${DATABASE_PASSWORD:?set DATABASE_PASSWORD in .env}
|
|
# Matches the manual path (createdb --encoding UTF8). Arabic content is
|
|
# stored as UTF-8 either way; being explicit keeps the two paths identical.
|
|
POSTGRES_INITDB_ARGS: "--encoding=UTF8"
|
|
volumes:
|
|
- postgres-data:/var/lib/postgresql/data
|
|
ports:
|
|
# Published on 5433 by default so it cannot collide with a PostgreSQL
|
|
# already running on the host's 5432.
|
|
- "127.0.0.1:${POSTGRES_PUBLISHED_PORT:-5433}:5432"
|
|
healthcheck:
|
|
# pg_isready also fails while the entrypoint's temporary init server is
|
|
# up, so `service_healthy` really does mean "ready for connections".
|
|
test:
|
|
["CMD-SHELL", "pg_isready -U ${DATABASE_USERNAME:-postgres} -d ${DATABASE_NAME:-sharper_cms} -h 127.0.0.1"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 12
|
|
start_period: 15s
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Strapi 5 - REST API and admin panel. Waits for a database that is actually
|
|
# accepting connections, then runs its own migrations and the boot-time
|
|
# reconciliation in apps/cms/src/bootstrap (locales + public permissions).
|
|
# --------------------------------------------------------------------------
|
|
cms:
|
|
build:
|
|
context: .
|
|
dockerfile: apps/cms/Dockerfile
|
|
image: sharper-cms:local
|
|
restart: unless-stopped
|
|
# The healthcheck this service is judged by (GET /_health) is declared in
|
|
# apps/cms/Dockerfile, so `docker run` gets it too.
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
environment:
|
|
NODE_ENV: production
|
|
HOST: 0.0.0.0
|
|
PORT: 1337
|
|
IS_PROXIED: ${IS_PROXIED:-false}
|
|
# Absolute URL the admin panel and generated links use. Change it
|
|
# together with CMS_PORT, and set it to the public origin in production.
|
|
PUBLIC_URL: ${CMS_PUBLIC_URL:-http://localhost:1337}
|
|
LOG_LEVEL: ${LOG_LEVEL:-info}
|
|
PARTNER_REVIEW_NOTIFICATION_EMAILS: ${PARTNER_REVIEW_NOTIFICATION_EMAILS:-}
|
|
|
|
# Inside the compose network the database is reachable by service name on
|
|
# its own port, never through the published one.
|
|
DATABASE_CLIENT: postgres
|
|
DATABASE_HOST: postgres
|
|
DATABASE_PORT: 5432
|
|
DATABASE_NAME: ${DATABASE_NAME:-sharper_cms}
|
|
DATABASE_USERNAME: ${DATABASE_USERNAME:-postgres}
|
|
DATABASE_PASSWORD: ${DATABASE_PASSWORD:?set DATABASE_PASSWORD in .env}
|
|
DATABASE_SSL: "false"
|
|
|
|
# Generate all six with: ./scripts/generate-secrets.sh --write .env
|
|
APP_KEYS: ${APP_KEYS:?run ./scripts/generate-secrets.sh --write .env}
|
|
API_TOKEN_SALT: ${API_TOKEN_SALT:?run ./scripts/generate-secrets.sh --write .env}
|
|
ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET:?run ./scripts/generate-secrets.sh --write .env}
|
|
JWT_SECRET: ${JWT_SECRET:?run ./scripts/generate-secrets.sh --write .env}
|
|
TRANSFER_TOKEN_SALT: ${TRANSFER_TOKEN_SALT:?run ./scripts/generate-secrets.sh --write .env}
|
|
ENCRYPTION_KEY: ${ENCRYPTION_KEY:?run ./scripts/generate-secrets.sh --write .env}
|
|
|
|
# Browser origins allowed to call the API directly.
|
|
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000}
|
|
volumes:
|
|
# Media Library files. Like the database, they survive `down` and are
|
|
# only removed by `down -v`.
|
|
- cms-uploads:/repo/apps/cms/public/uploads
|
|
ports:
|
|
- "127.0.0.1:${CMS_PORT:-1337}:1337"
|
|
|
|
# --------------------------------------------------------------------------
|
|
# React Router 8 server-side renderer. Waits for a CMS that answers /_health,
|
|
# so the first page render already has content. (Pages do fall back to their
|
|
# bundled draft copy if the CMS goes away later - the site never 500s because
|
|
# Strapi is down.)
|
|
# --------------------------------------------------------------------------
|
|
web:
|
|
build:
|
|
context: .
|
|
dockerfile: apps/web/Dockerfile
|
|
image: sharper-web:local
|
|
restart: unless-stopped
|
|
# Healthcheck (GET /en) is declared in apps/web/Dockerfile.
|
|
depends_on:
|
|
cms:
|
|
condition: service_healthy
|
|
environment:
|
|
NODE_ENV: production
|
|
PORT: 3000
|
|
# Server-side fetches go to the service name on the compose network.
|
|
# This origin is also prefixed onto uploaded media URLs that reach the
|
|
# browser - see "Images from the CMS" in README.md.
|
|
STRAPI_URL: ${DOCKER_STRAPI_URL:-http://cms:1337}
|
|
STRAPI_TOKEN: ${STRAPI_TOKEN:-}
|
|
# Canonical URLs, hreflang alternates and JSON-LD are built from this.
|
|
SITE_URL: ${SITE_URL:-http://localhost:3000}
|
|
# Cloudflare Turnstile on the public forms - off unless both are set.
|
|
TURNSTILE_SITE_KEY: ${TURNSTILE_SITE_KEY:-}
|
|
TURNSTILE_SECRET_KEY: ${TURNSTILE_SECRET_KEY:-}
|
|
volumes:
|
|
# Panton and the Arabic faces are licensed and deliberately excluded from
|
|
# the image (.dockerignore). Mounting the host folder read-only lets a
|
|
# local container use them without ever baking them into an artefact.
|
|
- ./apps/web/public/fonts:/repo/apps/web/build/client/fonts:ro
|
|
ports:
|
|
- "127.0.0.1:${WEB_PORT:-3000}:3000"
|
|
|
|
volumes:
|
|
postgres-data:
|
|
cms-uploads:
|