Files
warmbly/deploy/check-coolify.sh
T
Matthew MeszarosandDevin AI 9af270a533 feat: add production image-only Coolify Compose deployment with domain, secret, backup and setup instructions
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-16 11:47:52 +00:00

61 lines
5.2 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.."
compose=(docker compose --env-file /dev/null -f deploy/docker-compose.coolify.yml)
# Disposable interpolation fixtures, never deployment credentials.
export AUTH_SECRET=coolify-validation-auth-secret-at-least-32-characters
export INTERNAL_API_TOKEN=coolify-validation-edge-token
export NODE_BROKER_TOKEN=coolify-validation-worker-token
export SECRET_KEY_BASE=coolify-validation-phoenix-secret-at-least-sixty-four-characters-long
export KMS_LOCAL_MASTER_KEY=Y29vbGlmeS12YWxpZGF0aW9uLW9ubHktMzItYnl0ZXM=
export CREDENTIALS_ENCRYPTION_KEY=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
export POSTGRES_PASSWORD=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
export APP_URL=https://app.example.com
export ADMIN_URL=https://admin.example.com
export API_PUBLIC_URL=https://api.example.com
export REALTIME_HOST=realtime.example.com
export TRACKING_DOMAIN=track.example.com
export FORMS_DOMAIN=forms.example.com
export WARMBLY_TAG=coolify-validation
config=$("${compose[@]}" config --format json)
check() {
if ! jq -e "$1" >/dev/null <<<"$config"; then
printf 'Coolify check failed: %s\n' "$2" >&2
exit 1
fi
}
check '(.services | keys) == ["admin", "backend", "consumer", "forms", "nats", "postgres", "realtime", "redis", "tracking", "web", "worker"]' 'complete stack'
check 'all(.services[]; (has("build") or has("ports") or has("env_file") or has("container_name") or has("privileged")) | not)' 'no builds, published ports, external env files, fixed container names, or privileges'
check 'all(.services[].volumes[]?; .type == "volume")' 'named volumes only, no host or Docker socket mounts'
check '(.volumes | keys) == ["blobs", "nats_data", "postgres_data", "redis_data", "worker_state"]' 'all persistent stores'
check '. as $config | all(["backend", "consumer", "worker", "forms", "tracking", "realtime", "web", "admin"][]; $config.services[.].image == "ghcr.io/warmbly/warmbly/" + . + ":coolify-validation")' 'matching service images with one release tag'
check 'all([.services.backend, .services.consumer, .services.worker, .services.tracking, .services.forms, .services.realtime][]; .environment.APP_ENV == "prod")' 'production environment'
check '.services.backend.environment.AUTH_SECRET == .services.realtime.environment.JWT_SECRET' 'shared JWT secret'
check '.services.backend.environment.NODE_BROKER_TOKEN == .services.worker.environment.ENCRYPTED_KEYS_WORKER_TOKEN and .services.tracking.environment.INTERNAL_API_TOKEN == .services.backend.environment.INTERNAL_API_TOKEN and .services.forms.environment.INTERNAL_API_TOKEN == .services.backend.environment.INTERNAL_API_TOKEN' 'separate worker and edge tokens'
check '.services.worker.environment.MAIL_TLS_INSECURE == "false" and .services.worker.environment.ENCRYPTED_KEYS_PROVIDER == "http" and (.services.worker.environment | has("PRIMARY_DB") | not)' 'worker TLS and database boundary'
check '.services.postgres.environment.POSTGRES_PASSWORD as $password | .services.backend.environment.PRIMARY_DB == .services.consumer.environment.PRIMARY_DB and .services.backend.environment.PRIMARY_DB == .services.realtime.environment.DATABASE_URL and (.services.backend.environment.PRIMARY_DB | contains($password))' 'database clients share credentials'
check '.services.backend.environment.UPDATER_URL == "none" and (.services | has("updater") | not)' 'Coolify owns updates'
check '.services.backend.environment.WEBSOCKET_URL == "wss://realtime.example.com/socket/websocket" and .services.web.environment.WEBSOCKET_URL == .services.backend.environment.WEBSOCKET_URL and .services.realtime.environment.CHECK_ORIGIN_HOSTS == "https://app.example.com,https://admin.example.com"' 'secure realtime URL and browser origins'
check '.services.web.environment.WARMBLY_API_URL == "https://api.example.com" and .services.admin.environment.WARMBLY_API_URL == "https://api.example.com"' 'runtime frontend API URLs'
check '.services.backend.environment.MAIL_TRANSPORT == .services.consumer.environment.MAIL_TRANSPORT' 'platform mail reaches notifications'
check 'all([.services.backend, .services.consumer][]; .environment.GEODB_PATH == "/data/blobs/geo/GeoLite2-City.mmdb")' 'GeoIP path configured without requiring an external database'
check 'all(["backend", "tracking", "realtime", "forms", "web", "admin"][] as $name | .services[$name].healthcheck.test; any(.[]; contains("http://127.0.0.1:")))' 'HTTP probes use the container IPv4 loopback'
check 'all(["consumer", "worker", "tracking", "realtime", "forms", "web", "admin"][] as $name | .services[$name].depends_on.backend.condition; . == "service_healthy")' 'wait for backend migrations'
for required in AUTH_SECRET INTERNAL_API_TOKEN NODE_BROKER_TOKEN SECRET_KEY_BASE KMS_LOCAL_MASTER_KEY CREDENTIALS_ENCRYPTION_KEY POSTGRES_PASSWORD APP_URL ADMIN_URL API_PUBLIC_URL REALTIME_HOST TRACKING_DOMAIN FORMS_DOMAIN; do
if env -u "$required" "${compose[@]}" config --quiet >/dev/null 2>&1; then
printf 'Coolify accepted missing required variable: %s\n' "$required" >&2
exit 1
fi
if env "$required=" "${compose[@]}" config --quiet >/dev/null 2>&1; then
printf 'Coolify accepted empty required variable: %s\n' "$required" >&2
exit 1
fi
done
printf 'Coolify Compose configuration checks passed.\n'