# Coolify Docker Compose Deployment Knowledge Source: - Local archive: `viewpagesourcedockercomposecoolify.html` - Official page: https://coolify.io/docs/applications/build-packs/docker-compose - Cross-check: Coolify docs via Context7, `/coollabsio/coolify-docs` - Last reviewed: 2026-06-11 This note summarizes how to prepare a project that uses Docker Compose for deployment in Coolify. Use it as a checklist for other projects before deploying. ## Core Model Coolify can deploy a project from a `docker-compose.yml` or `docker-compose.yaml` file. For Docker Compose deployments, the compose file becomes the main source of truth for services, build settings, environment variables, volumes, healthchecks, and service networking. Coolify also attaches its reverse proxy to the stack network so public traffic can reach the exposed application service. ## Recommended Compose Rules - Keep service names stable because services communicate by service name, for example `DB_HOST=db`. - Do not use `localhost` or `127.0.0.1` to connect from one container to another container. - Prefer `expose` for internal container ports and let Coolify handle public routing through its proxy. - Use `ports` only when you intentionally need host-level port binding. - Add `restart: unless-stopped` for long-running services. - Add healthchecks for services that must be healthy before the stack is considered ready. - Avoid hardcoded production secrets in the compose file. - Put persistent data in named volumes. - Do not define custom `networks:` unless you have a specific, tested reason. ## Networking Coolify automatically creates an isolated bridge network for each Compose stack. All services in the same stack can reach each other by service name. Example: ```yaml services: app: environment: DB_HOST: db db: image: mysql:8.0 ``` The `app` container connects to MySQL using `db:3306`, not `localhost:3306`. ## Do Not Define Custom Networks For normal Coolify Compose deployments, remove custom networks: ```yaml # Avoid this in Coolify unless you know exactly why it is needed. services: frontend: networks: - my-network backend: networks: - my-network networks: my-network: driver: bridge ``` Why: Coolify already creates a managed network and connects Traefik to it. Extra custom networks can cause intermittent routing problems because the proxy may select an unreachable container IP. Symptoms include hanging requests and `504 Gateway Timeout`. Use the auto-created network unless you need cross-stack communication. For cross-stack communication, use Coolify's predefined network option and reference the full remote service name when required. ## Environment Variables Coolify parses compose environment variables and exposes many of them in its UI. Common forms: ```yaml environment: APP_ENV: production APP_DEBUG: "${APP_DEBUG:-false}" APP_KEY: "${APP_KEY:?Set APP_KEY in Coolify}" DB_PASSWORD: "${DB_PASSWORD:?Set DB_PASSWORD in Coolify}" ``` Meaning: - `VALUE`: hardcoded into the container. - `${VAR}`: comes from Coolify/project environment. - `${VAR:-default}`: uses `default` if unset. - `${VAR:?message}`: required; deployment should fail early if unset. For production, use required variables for secrets so a deploy cannot silently start with weak defaults. ## Magic Environment Variables Coolify supports magic variables with the `SERVICE__` pattern. They can generate URLs, FQDNs, passwords, users, base64 strings, and hex strings. Examples: ```yaml environment: APP_URL: "${SERVICE_URL_APP_80}" DB_PASSWORD: "${SERVICE_PASSWORD_64_MYSQL}" ``` Common types: - `SERVICE_URL_*` - `SERVICE_FQDN_*` - `SERVICE_PASSWORD_*` - `SERVICE_PASSWORD_64_*` - `SERVICE_PASSWORDWITHSYMBOLS_64_*` - `SERVICE_BASE64_*` - `SERVICE_HEX_*` Use these only when you intentionally want Coolify to create and own that variable. Important pitfall: if a magic variable is declared in `docker-compose.yaml`, Coolify may block deletion from the UI with a message like: ```text Cannot delete environment variable 'SERVICE_FQDN_APP' Please remove it from the Docker Compose file first. ``` Fix: 1. Remove the related `SERVICE_FQDN_*` or `SERVICE_URL_*` entry from `docker-compose.yaml`. 2. Redeploy or resync the resource in Coolify. 3. Delete the variable again from the Coolify UI. ## Domains and Public Ports For web apps, Coolify needs to know which service and port should receive public traffic. Recommended project-side setup: ```yaml services: app: expose: - "80" ``` Then configure the domain/proxy in Coolify to target service `app` on port `80`. Do not add `SERVICE_URL_APP_80` just to expose the app if you prefer managing domains in the Coolify UI. Declaring `SERVICE_URL_*` makes Coolify manage related magic variables. ## Volumes and Persistent Data Use named volumes for database and persistent application data: ```yaml services: db: image: mysql:8.0 volumes: - mysql-data:/var/lib/mysql volumes: mysql-data: ``` For application uploads, logs, or generated files, map only the directories that must persist. Do not persist build output unless the app requires runtime writes there. ## Healthchecks Use healthchecks for databases and web services: ```yaml services: app: healthcheck: test: ["CMD-SHELL", "curl -fsS http://127.0.0.1/ >/dev/null || exit 1"] interval: 30s timeout: 5s retries: 3 start_period: 30s db: healthcheck: test: ["CMD-SHELL", "mysqladmin ping -h localhost -uroot -p\"$${MYSQL_ROOT_PASSWORD}\" --silent"] interval: 5s timeout: 5s retries: 20 start_period: 30s ``` Use `depends_on` with `condition: service_healthy` when the app must wait for the database: ```yaml services: app: depends_on: db: condition: service_healthy ``` For one-time jobs or migration-only services, Coolify supports excluding a service from overall healthchecks: ```yaml services: migrate: exclude_from_hc: true ``` ## Laravel Notes For Laravel projects: - Use a real web server in production, such as Apache or Nginx with PHP-FPM. - Do not use `php artisan serve` as the production command. - Set `APP_ENV=production`. - Set `APP_DEBUG=false`. - Set `APP_KEY` in Coolify. - Set `APP_URL` to the final public URL. - Use `DB_HOST` as the Compose service name, usually `db`. - Run migrations intentionally during entrypoint/deploy, and ensure seeders are idempotent if they run on every deploy. - Keep `.env` out of Git; define production values in Coolify environment variables. ## MySQL Password Warning MySQL initializes credentials only when the database volume is first created. If you change `MYSQL_PASSWORD`, `MYSQL_ROOT_PASSWORD`, or `DB_PASSWORD` after the volume already exists, the existing database users may still use the old password. If the app cannot connect after changing passwords: - Set `DB_PASSWORD` back to the password used when the volume was initialized, or - Rotate the database password manually inside MySQL, or - Recreate the database volume if data can be deleted. ## Minimal Production Template ```yaml services: app: build: context: . dockerfile: Dockerfile restart: unless-stopped expose: - "80" environment: APP_ENV: "${APP_ENV:-production}" APP_DEBUG: "${APP_DEBUG:-false}" APP_URL: "${APP_URL:?Set APP_URL in Coolify}" APP_KEY: "${APP_KEY:?Set APP_KEY in Coolify}" DB_CONNECTION: mysql DB_HOST: "${DB_HOST:-db}" DB_PORT: "${DB_PORT:-3306}" DB_DATABASE: "${DB_DATABASE:-app}" DB_USERNAME: "${DB_USERNAME:-app}" DB_PASSWORD: "${DB_PASSWORD:?Set DB_PASSWORD in Coolify}" depends_on: db: condition: service_healthy healthcheck: test: ["CMD-SHELL", "curl -fsS http://127.0.0.1/ >/dev/null || exit 1"] interval: 30s timeout: 5s retries: 3 start_period: 30s db: image: mysql:8.0 restart: unless-stopped environment: MYSQL_DATABASE: "${DB_DATABASE:-app}" MYSQL_USER: "${DB_USERNAME:-app}" MYSQL_PASSWORD: "${DB_PASSWORD:?Set DB_PASSWORD in Coolify}" MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD:?Set DB_ROOT_PASSWORD in Coolify}" volumes: - mysql-data:/var/lib/mysql healthcheck: test: ["CMD-SHELL", "mysqladmin ping -h localhost -uroot -p\"$${MYSQL_ROOT_PASSWORD}\" --silent"] interval: 5s timeout: 5s retries: 20 start_period: 30s volumes: mysql-data: ``` ## Pre-Deploy Checklist - `docker compose config --quiet` passes locally. - `docker compose build app` passes locally or in CI. - App image exposes the same port configured in Coolify. - No custom `networks:` are defined for normal stacks. - Public app service has a healthcheck. - Database service has a healthcheck. - App uses database service hostname, not `localhost`. - Required secrets are set in Coolify. - No weak fallback secrets remain for production. - Persistent data is stored in named volumes. - Magic `SERVICE_*` variables are only present when intentionally managed by Coolify. ## Troubleshooting ### Cannot delete `SERVICE_FQDN_APP` Cause: a related magic variable is still declared in compose, commonly `SERVICE_FQDN_APP`, `SERVICE_URL_APP`, or `SERVICE_URL_APP_80`. Fix: remove it from compose, redeploy/resync, then delete it in Coolify. ### 504 Gateway Timeout Check: - Remove custom networks. - Confirm Coolify routes to the correct service and port. - Confirm the app listens on `0.0.0.0`, not only `127.0.0.1`. - Check app healthcheck and container logs. ### App cannot connect to database Check: - `DB_HOST` is the database service name, for example `db`. - `DB_PORT=3306`. - `DB_DATABASE`, `DB_USERNAME`, and `DB_PASSWORD` match the database service. - Existing MySQL volume may still use an old password. ### Environment value keeps coming back Check whether that variable is declared in `docker-compose.yaml`. Coolify treats compose-declared variables as managed by the compose file.