# Docker Deployment Rules These rules were compiled from real failures during a Laravel + Coolify deployment. Apply them to any Docker/Laravel project. --- ## 1. Pin image tags, never use rolling tags Don't use `composer:2`, `node:20`, `php:8.2-fpm-alpine` as rolling tags. They change over time and break builds months later. Use the same PHP version as your local environment. ```dockerfile # BROKEN — will drift FROM composer:2 AS composer # GOOD — same PHP version as runtime FROM php:8.2-cli-alpine AS composer ``` ## 2. Verify PHP extensions from composer.lock, not assumptions Don't guess which extensions are needed. Check what's actually declared: ```bash grep 'ext-' composer.lock | sort -u ``` The `php:8.2-fpm-alpine` base image already has most extensions compiled in. Only install what's missing (typically just `pdo_mysql`). ```dockerfile # BROKEN — listing 14 extensions, most already compiled in RUN docker-php-ext-install pdo_mysql ctype curl dom fileinfo filter hash json libxml mbstring openssl pcre session tokenizer # GOOD — only what's actually missing RUN docker-php-ext-install pdo_mysql ``` ## 3. Shared services need shared volumes If Nginx and PHP-FPM are separate containers, Nginx cannot see the app container's files. Use a named volume shared between both: ```yaml volumes: app-public: # shared between app and nginx app-storage: # shared between app and nginx services: nginx: volumes: - app-public:/var/www/html/public:ro - app-storage:/var/www/html/storage:ro app: volumes: - app-public:/var/www/html/public - app-storage:/var/www/html/storage ``` ## 4. Never use bash variable interpolation in PHP inline code Inline PHP in entrypoint scripts must not use `$VARIABLE` inside double-quoted strings. Passwords containing `$`, `\`, or `"` will break bash parsing. ```bash # BROKEN — password with $, \, " breaks php -r "new PDO('...', '${DB_PASSWORD}');" # SAFE — getenv() avoids bash expansion php -r "new PDO('...', getenv('DB_PASSWORD'));" ``` ## 5. Bind mounts fail in Coolify Coolify resolves compose-relative paths differently from local Docker. Bind-mounting individual config files often fails with "not a directory". Bake configs into a custom image instead. ```yaml # BROKEN — fails in Coolify nginx: image: nginx:alpine volumes: - ./docker-nginx.conf:/etc/nginx/conf.d/default.conf:ro # GOOD — config baked into image nginx: build: dockerfile: Dockerfile.nginx ``` ```dockerfile # Dockerfile.nginx FROM nginx:1.25-alpine COPY docker-nginx.conf /etc/nginx/conf.d/default.conf ``` ## 6. Don't add Traefik labels to docker-compose in Coolify Coolify auto-generates Traefik routing labels. Adding them manually causes conflicts or is ignored entirely. ```yaml # DON'T — Coolify handles this labels: - "traefik.enable=true" - "traefik.http.routers.aigo.rule=Host(`yourdomain.com`)" - "traefik.http.services.aigo.loadbalancer.server.port=80" ``` ## 7. Match the database image to the dump source Check the SQL dump header to determine the actual database engine and version: ```sql -- MariaDB dump 10.19 Distrib 10.6.25-MariaDB -- Server version 10.6.25-MariaDB ``` Use the matching image — MariaDB and MySQL are not interchangeable for dump imports. ```yaml # Source is MariaDB 10.6 image: mariadb:10.6 # Source is MySQL 8.0 image: mysql:8.0 ``` ## 8. MariaDB/mysql client needs `--ssl=0` inside containers The `mariadb-client` package defaults to SSL. MySQL 8.0 and MariaDB containers have self-signed certs, causing connection failures. ```bash # BROKEN — SSL error with self-signed cert mysql -h mysql -u user -ppassword dbname < dump.sql # GOOD — skip SSL for local container connections mysql -h mysql --ssl=0 -u user -ppassword dbname < dump.sql ``` ## 9. Review the full entrypoint flow for restart loops `set -e` means any command failure exits the script → PHP-FPM never starts → Docker restarts indefinitely. Every step is a potential crash point. ```bash #!/bin/sh set -e # 1. Wait for MySQL — check PDO connects # 2. Import SQL — check --ssl=0 and credentials # 3. config:cache — check APP_KEY is set # 4. storage:link — check permissions # 5. route:cache, view:cache — check directories are writable # 6. exec php-fpm — only this should run as the final process ``` ## 10. Healtcheck use mariadb default healthcheck image script if available. if not available ask user first if you want to make a healthcheck for mariadb image.