153 lines
4.5 KiB
Markdown
153 lines
4.5 KiB
Markdown
# 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.
|