526e0f280a
Enabling Docker took three steps because the production image runs as `node` and the socket is mode 660 root:docker, so a matching group was required. The group id is host-specific — 0 on Docker Desktop, commonly 999 on Debian/Ubuntu, 993 on Amazon Linux 2023 — and a wrong value is a silent EACCES that shows up as "Docker never appears in the dashboard". There is no value that is right everywhere, so there is no default to ship. The container can read it off the socket. The runner stage no longer sets `USER node`; a new entrypoint starts as root, takes the socket's group with `stat -c %g`, joins it, and execs the command through `su-exec node`. The app process is still `node` and `tini` is still PID 1. With no socket mounted it drops to `node` and does nothing else, and a deployment that pins `user:` is left alone. `group_add` and `DOCKER_GID` are gone from all five compose files, `.env.example`, the docs in four languages, the dashboard guide (three steps to two) and the not-configured API message. Uncommenting one volume line is now the whole edit. The mount stays opt-in: the socket is root-equivalent on the host, and on by default would hand every deployment's backend the ability to take over its host whether or not it uses compute. Verified on a build of `target: runner` — no socket: uid 1000; socket at gid 0: joins root, writable; socket at gid 999 with the group already present: joins it, writable; and the old `USER node` path with no entrypoint: denied, which is the failure the removed step existed to avoid. Then end to end from that image against a live database: provider ready, metadata slice present, a service created and serving on its published port. Also batches the compute logs limiter test. 120 sequential requests overran the 10s timeout on a busy machine and 120 simultaneous ones had connections dropped; ten in flight is neither, and the assertion is by count rather than position. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
258 lines
11 KiB
YAML
258 lines
11 KiB
YAML
services:
|
|
postgres:
|
|
image: ghcr.io/insforge/postgres:v15.13.4
|
|
|
|
command: postgres -c config_file=/etc/postgresql/postgresql.conf -c app.encryption_key='${ENCRYPTION_KEY:-${JWT_SECRET:-dev-secret-please-change-in-production}}'
|
|
environment:
|
|
- POSTGRES_USER=${POSTGRES_USER:-postgres}
|
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
|
|
- POSTGRES_DB=${POSTGRES_DB:-insforge}
|
|
volumes:
|
|
- postgres-data:/var/lib/postgresql/data
|
|
- ./deploy/docker-init/db/db-init.sql:/docker-entrypoint-initdb.d/01-init.sql
|
|
- ./deploy/docker-init/db/jwt.sql:/docker-entrypoint-initdb.d/02-jwt.sql
|
|
- ./deploy/docker-init/db/postgresql.conf:/etc/postgresql/postgresql.conf
|
|
ports:
|
|
- "127.0.0.1:${POSTGRES_PORT:-5432}:5432"
|
|
networks:
|
|
- insforge-network
|
|
healthcheck:
|
|
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 5
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
|
|
postgrest:
|
|
image: postgrest/postgrest:v12.2.12
|
|
|
|
restart: unless-stopped
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
environment:
|
|
#POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
|
#POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
|
|
#POSTGRES_DB: ${POSTGRES_DB:-insforge}
|
|
PGRST_DB_URI: postgres://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-insforge}
|
|
PGRST_OPENAPI_SERVER_PROXY_URI: http://localhost:3000
|
|
PGRST_DB_SCHEMA: public
|
|
PGRST_DB_ANON_ROLE: anon
|
|
# Keep in sync with the backend's POSTGREST_MAX_SOCKETS (default 50)
|
|
PGRST_DB_POOL: ${PGRST_DB_POOL:-50}
|
|
PGRST_JWT_SECRET: ${JWT_SECRET:-dev-secret-please-change-in-production}
|
|
ports:
|
|
- "127.0.0.1:${POSTGREST_PORT:-5430}:3000"
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
networks:
|
|
- insforge-network
|
|
|
|
insforge:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
target: runner
|
|
args:
|
|
VITE_API_BASE_URL: ${VITE_API_BASE_URL:-http://localhost:7130}
|
|
VITE_PUBLIC_POSTHOG_KEY: ${VITE_PUBLIC_POSTHOG_KEY:-}
|
|
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
ports:
|
|
- "${APP_PORT:-7130}:7130"
|
|
- "127.0.0.1:${AUTH_PORT:-7131}:7131"
|
|
environment:
|
|
- PORT=7130
|
|
- PROJECT_ROOT=/app
|
|
- API_BASE_URL=${API_BASE_URL:-}
|
|
- JWT_SECRET=${JWT_SECRET:-dev-secret-please-change-in-production}
|
|
- ENCRYPTION_KEY=${ENCRYPTION_KEY:-}
|
|
- ROOT_ADMIN_USERNAME=${ROOT_ADMIN_USERNAME:-${ADMIN_EMAIL:-admin}}
|
|
- ROOT_ADMIN_PASSWORD=${ROOT_ADMIN_PASSWORD:-${ADMIN_PASSWORD:-change-this-password}}
|
|
- ADMIN_EMAIL=${ROOT_ADMIN_USERNAME:-${ADMIN_EMAIL:-admin}}
|
|
- ADMIN_PASSWORD=${ROOT_ADMIN_PASSWORD:-${ADMIN_PASSWORD:-change-this-password}}
|
|
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}
|
|
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}
|
|
# PostgreSQL connection
|
|
- POSTGRES_HOST=postgres
|
|
- POSTGRES_PORT=5432
|
|
- POSTGRES_DB=${POSTGRES_DB:-insforge}
|
|
- POSTGRES_USER=${POSTGRES_USER:-postgres}
|
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
|
|
- DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-insforge}
|
|
- POSTGREST_BASE_URL=http://postgrest:3000
|
|
- POSTGREST_MAX_SOCKETS=${POSTGREST_MAX_SOCKETS:-}
|
|
- POSTGREST_MAX_FREE_SOCKETS=${POSTGREST_MAX_FREE_SOCKETS:-}
|
|
- POSTGREST_FREE_SOCKET_TIMEOUT_MS=${POSTGREST_FREE_SOCKET_TIMEOUT_MS:-}
|
|
# Deno Runtime URL for serverless functions
|
|
- DENO_RUNTIME_URL=http://deno:7133
|
|
- DENO_DEPLOY_TOKEN=${DENO_DEPLOY_TOKEN:-}
|
|
- DENO_DEPLOY_ORG_ID=${DENO_DEPLOY_ORG_ID:-}
|
|
# Storage Configuration
|
|
- MAX_FILE_SIZE=${MAX_FILE_SIZE:-}
|
|
- MAX_JSON_BODY_SIZE=${MAX_JSON_BODY_SIZE:-}
|
|
- MAX_URLENCODED_BODY_SIZE=${MAX_URLENCODED_BODY_SIZE:-}
|
|
- KEEP_ALIVE_TIMEOUT_MS=${KEEP_ALIVE_TIMEOUT_MS:-}
|
|
# S3-compatible storage (MinIO, RustFS, Wasabi, Tencent COS, Aliyun OSS,
|
|
# R2 ...). S3_BUCKET/S3_REGION preferred; AWS_* are the legacy names
|
|
# kept for cloud provisioning and existing .env files.
|
|
- S3_BUCKET=${S3_BUCKET:-}
|
|
- S3_REGION=${S3_REGION:-}
|
|
- AWS_S3_BUCKET=${AWS_S3_BUCKET:-}
|
|
- AWS_REGION=${AWS_REGION:-}
|
|
- S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-}
|
|
- S3_ACCESS_KEY_ID=${S3_ACCESS_KEY_ID:-}
|
|
- S3_SECRET_ACCESS_KEY=${S3_SECRET_ACCESS_KEY:-}
|
|
- S3_FORCE_PATH_STYLE=${S3_FORCE_PATH_STYLE:-}
|
|
- S3_MAX_OBJECT_SIZE_BYTES=${S3_MAX_OBJECT_SIZE_BYTES:-}
|
|
- S3_USE_PRESIGNED_URLS=${S3_USE_PRESIGNED_URLS:-}
|
|
- AWS_CLOUDFRONT_URL=${AWS_CLOUDFRONT_URL:-}
|
|
- AWS_CLOUDFRONT_KEY_PAIR_ID=${AWS_CLOUDFRONT_KEY_PAIR_ID:-}
|
|
- AWS_CLOUDFRONT_PRIVATE_KEY=${AWS_CLOUDFRONT_PRIVATE_KEY:-}
|
|
# Deployment Configuration
|
|
- VERCEL_TOKEN=${VERCEL_TOKEN:-}
|
|
- VERCEL_TEAM_ID=${VERCEL_TEAM_ID:-}
|
|
- VERCEL_PROJECT_ID=${VERCEL_PROJECT_ID:-}
|
|
# Multi-tenant Cloud Configuration
|
|
- DEPLOYMENT_ID=${DEPLOYMENT_ID:-}
|
|
- PROJECT_ID=${PROJECT_ID:-}
|
|
- APP_KEY=${APP_KEY:-}
|
|
- ACCESS_API_KEY=${ACCESS_API_KEY:-}
|
|
- ACCESS_ANON_KEY=${ACCESS_ANON_KEY:-}
|
|
# LLM Model API keys
|
|
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-}
|
|
# Stripe Payments Configuration
|
|
- STRIPE_TEST_SECRET_KEY=${STRIPE_TEST_SECRET_KEY:-}
|
|
- STRIPE_LIVE_SECRET_KEY=${STRIPE_LIVE_SECRET_KEY:-}
|
|
# OAuth Configuration
|
|
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-}
|
|
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET:-}
|
|
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID:-}
|
|
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET:-}
|
|
- DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID:-}
|
|
- DISCORD_CLIENT_SECRET=${DISCORD_CLIENT_SECRET:-}
|
|
- MICROSOFT_CLIENT_ID=${MICROSOFT_CLIENT_ID:-}
|
|
- MICROSOFT_CLIENT_SECRET=${MICROSOFT_CLIENT_SECRET:-}
|
|
- LINKEDIN_CLIENT_ID=${LINKEDIN_CLIENT_ID:-}
|
|
- LINKEDIN_CLIENT_SECRET=${LINKEDIN_CLIENT_SECRET:-}
|
|
- X_CLIENT_ID=${X_CLIENT_ID:-}
|
|
- X_CLIENT_SECRET=${X_CLIENT_SECRET:-}
|
|
- APPLE_CLIENT_ID=${APPLE_CLIENT_ID:-}
|
|
- APPLE_CLIENT_SECRET=${APPLE_CLIENT_SECRET:-}
|
|
# Logs directory
|
|
- LOGS_DIR=/insforge-logs
|
|
# Anonymous telemetry (set INSFORGE_TELEMETRY_DISABLED=1 to opt out)
|
|
- INSFORGE_TELEMETRY_DISABLED=${INSFORGE_TELEMETRY_DISABLED:-}
|
|
- INSFORGE_DEPLOYMENT_METHOD=docker-compose
|
|
# Storage directory (for local file storage when S3 is not configured)
|
|
- STORAGE_DIR=/insforge-storage
|
|
# ─── Custom Compute: Docker provider (opt-in) ──────────────────────────
|
|
# Mounting the Docker socket below is what enables it: the driver registers
|
|
# itself when the socket is reachable, so nothing runs containers on your
|
|
# host unless you deliberately grant this.
|
|
#
|
|
# Read the security note in the compute docs first. The driver constructs
|
|
# every container spec itself and never forwards caller-supplied options, so
|
|
# a leaked API key cannot request a privileged container — but the socket is
|
|
# still root-equivalent on the host.
|
|
- COMPUTE_PROVIDER=${COMPUTE_PROVIDER:-}
|
|
# Socket the driver dials, mounted at the same path below so both sides
|
|
# agree. Override for rootless Docker or Podman.
|
|
- DOCKER_SOCKET_PATH=${DOCKER_SOCKET_PATH:-}
|
|
# Ingress default for services that do not choose one. `none` publishes no
|
|
# host port, which is right for workers and processors.
|
|
- COMPUTE_DEFAULT_INGRESS=${COMPUTE_DEFAULT_INGRESS:-none}
|
|
# Host address used to build URLs for `port` ingress. Unset, no URL is
|
|
# advertised rather than guessing an address that may not resolve.
|
|
- COMPUTE_PUBLIC_HOST=${COMPUTE_PUBLIC_HOST:-}
|
|
# Base domain for `host` ingress. Unset, a service that asks for a
|
|
# hostname gets no URL rather than an unroutable guess.
|
|
- COMPUTE_DOMAIN=${COMPUTE_DOMAIN:-}
|
|
# Bind address for published ports. Loopback by default — reaching a
|
|
# container from the internet should be a deliberate choice.
|
|
- COMPUTE_BIND_ADDRESS=${COMPUTE_BIND_ADDRESS:-127.0.0.1}
|
|
# Ceiling on an uploaded source-build context. Express buffers the whole
|
|
# tarball before the handler runs, so this is a memory bound, not just a
|
|
# policy — lower it on a small host.
|
|
- COMPUTE_BUILD_MAX_CONTEXT=${COMPUTE_BUILD_MAX_CONTEXT:-}
|
|
# Seconds an upload may send nothing before it is treated as stalled and
|
|
# cut loose. Only one build runs at a time, so a connection that stops
|
|
# making progress would otherwise block every other deploy. Resets on each
|
|
# chunk, so a slow but active link is never cut.
|
|
- COMPUTE_BUILD_UPLOAD_IDLE_TIMEOUT=${COMPUTE_BUILD_UPLOAD_IDLE_TIMEOUT:-}
|
|
# Keep compute containers off this project's network. Off by default:
|
|
# proximity to the database and storage is the point.
|
|
- COMPUTE_ISOLATE_NETWORK=${COMPUTE_ISOLATE_NETWORK:-}
|
|
volumes:
|
|
- storage-data:/insforge-storage
|
|
- shared-logs:/insforge-logs
|
|
# Uncomment to enable the Docker compute provider. The socket is
|
|
# root-equivalent on the host, so it is opt-in rather than on by default.
|
|
# Nothing else to set: the entrypoint reads the socket's group and joins it
|
|
# before dropping to the app user.
|
|
# - ${DOCKER_SOCKET_PATH:-/var/run/docker.sock}:${DOCKER_SOCKET_PATH:-/var/run/docker.sock}
|
|
restart: unless-stopped
|
|
networks:
|
|
- insforge-network
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
|
|
# Deno serverless runtime for edge functions
|
|
deno:
|
|
image: denoland/deno:alpine-2.0.6
|
|
|
|
working_dir: /app
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
depends_on:
|
|
- postgres
|
|
- postgrest
|
|
ports:
|
|
- "127.0.0.1:${DENO_PORT:-7133}:7133"
|
|
environment:
|
|
- PORT=7133
|
|
- DENO_ENV=${DENO_ENV:-production}
|
|
- DENO_DIR=/deno-dir
|
|
# PostgreSQL connection
|
|
- POSTGRES_HOST=postgres
|
|
- POSTGRES_PORT=5432
|
|
- POSTGRES_DB=${POSTGRES_DB:-insforge}
|
|
- POSTGRES_USER=${POSTGRES_USER:-postgres}
|
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
|
|
- POSTGREST_BASE_URL=http://postgrest:3000
|
|
# Worker timeout (60 seconds default)
|
|
- WORKER_TIMEOUT_MS=${WORKER_TIMEOUT_MS:-60000}
|
|
# Encryption keys for decrypting function secrets
|
|
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
|
|
- JWT_SECRET=${JWT_SECRET:-dev-secret-please-change-in-production}
|
|
volumes:
|
|
- ./functions:/app/functions
|
|
- deno_cache:/deno-dir
|
|
command: >
|
|
sh -c "
|
|
echo 'Downloading Deno dependencies...' &&
|
|
deno cache functions/server.ts &&
|
|
echo 'Starting Deno server on port 7133...' &&
|
|
deno run --unstable-worker-options --allow-net --allow-env --allow-read=./functions/worker-template.js functions/server.ts
|
|
"
|
|
restart: unless-stopped
|
|
networks:
|
|
- insforge-network
|
|
|
|
volumes:
|
|
postgres-data:
|
|
driver: local
|
|
deno_cache:
|
|
driver: local
|
|
storage-data:
|
|
driver: local
|
|
shared-logs:
|
|
driver: local
|
|
|
|
networks:
|
|
insforge-network:
|
|
driver: bridge
|