Files
cleftlipdata/AGENTS.MD
T
2026-06-06 10:05:39 +07:00

4.5 KiB

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.

# 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:

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).

# 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:

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.

# 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.

# 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.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.

# 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:

-- 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.

# 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.

# 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.

#!/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.