Created
March 7, 2026 00:22
-
-
Save hahagu/d501a86381f5edca70a76ae608f89781 to your computer and use it in GitHub Desktop.
Self Hosted Convex Docker Compose for Coolify
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Convex Self-Hosted — Coolify Docker Compose | |
| # Deploy as a "Docker Compose" resource in Coolify. | |
| # | |
| # After deployment: | |
| # 1. docker compose exec backend ./generate_admin_key.sh | |
| # 2. Set CONVEX_SELF_HOSTED_ADMIN_KEY in your app's .env with the generated key | |
| # 3. Set CONVEX_SELF_HOSTED_URL to the backend URL (SERVICE_URL_BACKEND_3210) | |
| # | |
| # Configuration: | |
| # Set INSTANCE_NAME in Coolify UI to your project name (default: "convex"). | |
| # This value is used as both the Convex instance name and the PostgreSQL database name. | |
| # | |
| # Proxy routes (all auto-generated by Coolify): | |
| # SERVICE_FQDN_BACKEND_3210 → backend:3210 (Convex API) | |
| # SERVICE_FQDN_SITE → site:80 (nginx → backend:3211, Convex HTTP actions) | |
| # SERVICE_FQDN_DASHBOARD_6791 → dashboard:6791 (Convex Dashboard) | |
| # MINIO_SERVER_URL → minio:9000 (S3 API — Coolify special-case) | |
| # MINIO_BROWSER_REDIRECT_URL → minio:9001 (MinIO Console — Coolify special-case) | |
| # | |
| # Coolify magic variables: | |
| # - BARE declarations (e.g. `- SERVICE_FQDN_X_3000`) trigger FQDN generation | |
| # AND Traefik label creation. The identifier MUST match the service name. | |
| # - Declaring SERVICE_FQDN_X also auto-generates SERVICE_URL_X (and port variants). | |
| # - VALUE references (e.g. `VAR=${SERVICE_URL_X}`) only consume an | |
| # already-generated value — they do NOT trigger generation or Traefik labels. | |
| # - Variables are stored at the stack level and shared across all services. | |
| services: | |
| # ────────────────────────────────────────────── | |
| # PostgreSQL | |
| # ────────────────────────────────────────────── | |
| postgres: | |
| image: postgres:16-alpine | |
| restart: unless-stopped | |
| environment: | |
| - POSTGRES_USER=${SERVICE_USER_POSTGRES:-convex} | |
| - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} | |
| - POSTGRES_DB=${INSTANCE_NAME:-convex} | |
| volumes: | |
| - postgres-data:/var/lib/postgresql/data | |
| healthcheck: | |
| test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"] | |
| start_period: 20s | |
| interval: 30s | |
| retries: 5 | |
| timeout: 5s | |
| # ────────────────────────────────────────────── | |
| # MinIO (S3-compatible object storage) | |
| # ────────────────────────────────────────────── | |
| # NOTE: Official MinIO Docker images were discontinued Oct 2025. | |
| # Using community-maintained builds from coollabsio/minio. | |
| # | |
| # Coolify has special-case FQDN handling for MINIO_SERVER_URL and | |
| # MINIO_BROWSER_REDIRECT_URL — it auto-generates two separate FQDNs | |
| # and creates Traefik labels for both ports (9000 + 9001). | |
| minio: | |
| image: ghcr.io/coollabsio/minio:latest | |
| restart: unless-stopped | |
| command: server /data --console-address ":9001" | |
| environment: | |
| - MINIO_ROOT_USER=$SERVICE_USER_MINIO | |
| - MINIO_ROOT_PASSWORD=$SERVICE_PASSWORD_MINIO | |
| - MINIO_SERVER_URL=${MINIO_SERVER_URL} | |
| - MINIO_BROWSER_REDIRECT_URL=${MINIO_BROWSER_REDIRECT_URL} | |
| volumes: | |
| - minio-data:/data | |
| healthcheck: | |
| test: ["CMD", "mc", "ready", "local"] | |
| interval: 5s | |
| timeout: 20s | |
| retries: 10 | |
| # ────────────────────────────────────────────── | |
| # MinIO init (creates Convex buckets on first run) | |
| # ────────────────────────────────────────────── | |
| minio-init: | |
| image: minio/mc:latest | |
| restart: "no" | |
| exclude_from_hc: true | |
| depends_on: | |
| minio: | |
| condition: service_healthy | |
| environment: | |
| - MINIO_USER=$SERVICE_USER_MINIO | |
| - MINIO_PASS=$SERVICE_PASSWORD_MINIO | |
| entrypoint: > | |
| /bin/sh -c " | |
| mc alias set convex http://minio:9000 $${MINIO_USER} $${MINIO_PASS}; | |
| mc mb convex/convex-snapshot-exports --ignore-existing; | |
| mc mb convex/convex-snapshot-imports --ignore-existing; | |
| mc mb convex/convex-modules --ignore-existing; | |
| mc mb convex/convex-user-files --ignore-existing; | |
| mc mb convex/convex-search-indexes --ignore-existing; | |
| echo 'All Convex buckets ready'; | |
| " | |
| # ────────────────────────────────────────────── | |
| # Convex Backend | |
| # ────────────────────────────────────────────── | |
| backend: | |
| image: ghcr.io/get-convex/convex-backend:latest | |
| restart: unless-stopped | |
| stop_grace_period: 10s | |
| stop_signal: SIGINT | |
| environment: | |
| # --- Proxy route: identifier matches service name → Coolify generates FQDN + Traefik --- | |
| - SERVICE_FQDN_BACKEND_3210 | |
| # --- Core --- | |
| - INSTANCE_NAME=${INSTANCE_NAME:-convex} | |
| - INSTANCE_SECRET=${SERVICE_HEX_INSTANCE} | |
| # --- Public URLs (value refs — consume auto-generated URLs) --- | |
| - CONVEX_CLOUD_ORIGIN=${SERVICE_URL_BACKEND} | |
| - CONVEX_SITE_ORIGIN=${SERVICE_URL_SITE} | |
| # --- Database --- | |
| - POSTGRES_URL=postgresql://${SERVICE_USER_POSTGRES:-convex}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432 | |
| - DO_NOT_REQUIRE_SSL=true | |
| # --- S3 / MinIO --- | |
| - AWS_ACCESS_KEY_ID=$SERVICE_USER_MINIO | |
| - AWS_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_MINIO | |
| - AWS_REGION=${AWS_REGION:-us-east-1} | |
| - S3_ENDPOINT_URL=http://minio:9000 | |
| - AWS_S3_FORCE_PATH_STYLE=true | |
| - AWS_S3_DISABLE_CHECKSUMS=true | |
| - AWS_S3_DISABLE_SSE=true | |
| - S3_STORAGE_EXPORTS_BUCKET=convex-snapshot-exports | |
| - S3_STORAGE_SNAPSHOT_IMPORTS_BUCKET=convex-snapshot-imports | |
| - S3_STORAGE_MODULES_BUCKET=convex-modules | |
| - S3_STORAGE_FILES_BUCKET=convex-user-files | |
| - S3_STORAGE_SEARCH_BUCKET=convex-search-indexes | |
| # --- Performance tuning --- | |
| - APPLICATION_MAX_CONCURRENT_MUTATIONS=${APPLICATION_MAX_CONCURRENT_MUTATIONS:-16} | |
| - APPLICATION_MAX_CONCURRENT_QUERIES=${APPLICATION_MAX_CONCURRENT_QUERIES:-16} | |
| - APPLICATION_MAX_CONCURRENT_NODE_ACTIONS=${APPLICATION_MAX_CONCURRENT_NODE_ACTIONS:-16} | |
| - APPLICATION_MAX_CONCURRENT_V8_ACTIONS=${APPLICATION_MAX_CONCURRENT_V8_ACTIONS:-16} | |
| - DOCUMENT_RETENTION_DELAY=${DOCUMENT_RETENTION_DELAY:-172800} | |
| # --- Logging --- | |
| - RUST_LOG=${RUST_LOG:-info} | |
| - DISABLE_BEACON=${DISABLE_BEACON:-true} | |
| - DISABLE_METRICS_ENDPOINT=${DISABLE_METRICS_ENDPOINT:-true} | |
| volumes: | |
| - backend-data:/convex/data | |
| depends_on: | |
| postgres: | |
| condition: service_healthy | |
| minio: | |
| condition: service_healthy | |
| minio-init: | |
| condition: service_completed_successfully | |
| healthcheck: | |
| test: curl -f http://localhost:3210/version | |
| interval: 5s | |
| start_period: 10s | |
| # ────────────────────────────────────────────── | |
| # Site proxy (nginx → backend:3211) | |
| # ────────────────────────────────────────────── | |
| # Coolify requires the magic variable identifier to match the service name. | |
| # Since backend already uses SERVICE_FQDN_BACKEND for port 3210, the site | |
| # proxy (port 3211) runs as a separate nginx service named "site". | |
| site: | |
| image: nginx:alpine | |
| restart: unless-stopped | |
| environment: | |
| - SERVICE_FQDN_SITE | |
| entrypoint: ["/bin/sh", "-c"] | |
| command: | |
| - | | |
| cat > /etc/nginx/conf.d/default.conf <<'CONF' | |
| server { | |
| listen 80; | |
| server_name _; | |
| location /health { | |
| access_log off; | |
| default_type text/plain; | |
| return 200 'ok'; | |
| } | |
| location / { | |
| proxy_pass http://backend:3211; | |
| proxy_http_version 1.1; | |
| proxy_set_header Upgrade $$http_upgrade; | |
| proxy_set_header Connection "upgrade"; | |
| proxy_set_header Host $$host; | |
| proxy_set_header X-Real-IP $$remote_addr; | |
| proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for; | |
| proxy_set_header X-Forwarded-Proto $$scheme; | |
| } | |
| } | |
| CONF | |
| exec nginx -g 'daemon off;' | |
| healthcheck: | |
| test: ["CMD-SHELL", "wget -qO- http://site/health || exit 1"] | |
| interval: 10s | |
| timeout: 5s | |
| retries: 3 | |
| depends_on: | |
| backend: | |
| condition: service_healthy | |
| # ────────────────────────────────────────────── | |
| # Convex Dashboard | |
| # ────────────────────────────────────────────── | |
| dashboard: | |
| image: ghcr.io/get-convex/convex-dashboard:latest | |
| restart: unless-stopped | |
| stop_grace_period: 10s | |
| stop_signal: SIGINT | |
| environment: | |
| # Proxy route: identifier matches service name → Coolify generates FQDN + Traefik | |
| - SERVICE_FQDN_DASHBOARD_6791 | |
| # Points dashboard to the backend API (cross-service value ref) | |
| - NEXT_PUBLIC_DEPLOYMENT_URL=${SERVICE_URL_BACKEND} | |
| healthcheck: | |
| test: curl -f http://dashboard:6791 | |
| interval: 10s | |
| timeout: 5s | |
| retries: 3 | |
| depends_on: | |
| backend: | |
| condition: service_healthy | |
| volumes: | |
| postgres-data: | |
| minio-data: | |
| backend-data: |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Self Hosted Convex Docker Compose for Coolify
This is a docker compose for coolify, that allows you to self host a convex database on your own coolify instance.