diff --git a/backend/server.mjs b/backend/server.mjs index d57a157..b8e46a9 100644 --- a/backend/server.mjs +++ b/backend/server.mjs @@ -4,7 +4,11 @@ import { Pool } from 'pg' import { contactAutoReplyEmail, contactNotificationEmail, newsletterConfirmationEmail } from './email-templates.mjs' import { sendEmail as sendResendEmail } from './resend-email.mjs' -const databaseUrl = process.env.DATABASE_URL +// Coolify injects application variables into each Compose service before +// Docker performs nested interpolation. Prefer the dedicated role explicitly +// so a retained legacy DATABASE_URL can remain available for rollback without +// silently widening the API's database privileges. +const databaseUrl = process.env.API_DATABASE_URL || process.env.DATABASE_URL const isProduction = process.env.NODE_ENV === 'production' const allowedOrigins = String(process.env.ALLOWED_ORIGINS || process.env.ALLOWED_ORIGIN || '') .split(',') @@ -37,7 +41,7 @@ const adminPostgrestUrl = process.env.ADMIN_POSTGREST_URL?.trim().replace(/\/$/, const adminPostgrestApiKey = process.env.ADMIN_POSTGREST_API_KEY?.trim() const publicSiteUrl = (process.env.PUBLIC_SITE_URL?.trim() || allowedOrigins[0] || '').replace(/\/$/, '') const publicEmailSiteUrl = (process.env.PUBLIC_EMAIL_SITE_URL?.trim() || 'https://vezvision.com').replace(/\/$/, '') -if (!databaseUrl || allowedOrigins.length === 0) throw new Error('DATABASE_URL and ALLOWED_ORIGIN/ALLOWED_ORIGINS are required') +if (!databaseUrl || allowedOrigins.length === 0) throw new Error('API_DATABASE_URL/DATABASE_URL and ALLOWED_ORIGIN/ALLOWED_ORIGINS are required') if (isProduction && (!turnstileSecret || turnstileExpectedHostnames.length === 0)) { throw new Error('TURNSTILE_SECRET_KEY and TURNSTILE_EXPECTED_HOSTNAMES are required in production') } diff --git a/coolify-compose.yml b/coolify-compose.yml index 0b4c1bc..7a146f1 100644 --- a/coolify-compose.yml +++ b/coolify-compose.yml @@ -31,6 +31,9 @@ services: networks: vezvision_internal: aliases: [vezvision-api] + # The Coolify-managed PostgreSQL service is discoverable only on this + # network. The API still has no published port or Traefik labels. + coolify: {} security_opt: ["no-new-privileges:true"] cap_drop: [ALL] read_only: true @@ -54,17 +57,11 @@ services: networks: vezvision_internal: aliases: [vezvision-postgrest] + coolify: {} security_opt: ["no-new-privileges:true"] cap_drop: [ALL] read_only: true tmpfs: [/tmp] - healthcheck: - test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3001/ >/dev/null || exit 1"] - interval: 10s - timeout: 3s - retries: 5 - start_period: 10s - gateway: build: context: . @@ -74,7 +71,9 @@ services: api: condition: service_healthy postgrest: - condition: service_healthy + # The pinned PostgREST image is distroless and has no shell/wget for a + # Compose health command. PostgREST itself retries database startup. + condition: service_started expose: ["8080"] labels: - traefik.docker.network=coolify diff --git a/deploy/hetzner/docker-compose.yml b/deploy/hetzner/docker-compose.yml index 5008cd9..2ac69c5 100644 --- a/deploy/hetzner/docker-compose.yml +++ b/deploy/hetzner/docker-compose.yml @@ -14,7 +14,9 @@ services: ADMIN_POSTGREST_URL: ${ADMIN_POSTGREST_URL:-} ADMIN_POSTGREST_API_KEY: ${ADMIN_POSTGREST_API_KEY:-} PORT: 3000 - networks: [vezvision_internal] + # Coolify's managed PostgreSQL DNS name is available only on `coolify`. + # No port or reverse-proxy label is published for this service. + networks: [vezvision_internal, coolify] security_opt: ["no-new-privileges:true"] cap_drop: [ALL] read_only: true @@ -35,18 +37,11 @@ services: PGRST_DB_ANON_ROLE: anon PGRST_SERVER_HOST: 0.0.0.0 PGRST_SERVER_PORT: 3001 - networks: [vezvision_internal] + networks: [vezvision_internal, coolify] security_opt: ["no-new-privileges:true"] cap_drop: [ALL] read_only: true tmpfs: [/tmp] - healthcheck: - test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3001/ >/dev/null || exit 1"] - interval: 10s - timeout: 3s - retries: 5 - start_period: 10s - gateway: build: context: ../.. @@ -56,7 +51,7 @@ services: api: condition: service_healthy postgrest: - condition: service_healthy + condition: service_started volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro networks: [vezvision_internal, coolify]