feat(docker): add production docker-compose for Coolify deployment

Coolify builds with --project-directory set to the repo root, which broke
the dev compose file's relative paths. Adds a dedicated prod compose stack
(baked-in code, no bind mounts, no hardcoded secrets, static Nuxt build
served by nginx) so local dev and Coolify no longer share fragile path
assumptions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Tim Stollberg 2026-07-18 14:59:06 +07:00
parent 71730fef06
commit a95fac5c0d
6 changed files with 143 additions and 1 deletions

View file

@ -1,4 +1,4 @@
PHONY: dev stop test lint migrate fresh shell-php shell-node build
PHONY: dev stop test lint migrate fresh shell-php shell-node build prod-build prod-up prod-stop
dev:
docker compose -f docker/docker-compose.yml up -d
@ -26,3 +26,12 @@ shell-node:
build:
docker compose -f docker/docker-compose.yml exec frontend npm run build
prod-build:
docker compose -f docker/docker-compose.prod.yml --project-directory . build
prod-up:
docker compose -f docker/docker-compose.prod.yml --project-directory . up -d
prod-stop:
docker compose -f docker/docker-compose.prod.yml --project-directory . down

View file

@ -0,0 +1,55 @@
services:
backend:
build:
context: .
dockerfile: docker/php/Dockerfile.prod
environment:
APP_ENV: production
APP_DEBUG: "false"
APP_KEY: ${APP_KEY}
APP_URL: ${APP_URL}
DB_HOST: mysql
DB_DATABASE: ${DB_DATABASE}
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
RUN_MIGRATIONS: "true"
depends_on:
mysql:
condition: service_healthy
networks:
- mandant
nginx:
build:
context: .
dockerfile: docker/frontend/Dockerfile.prod
ports:
- "8899:80"
depends_on:
- backend
networks:
- mandant
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_USER: ${DB_USERNAME}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 5s
timeout: 5s
retries: 10
networks:
- mandant
volumes:
mysql_data:
networks:
mandant:
driver: bridge

View file

@ -0,0 +1,14 @@
FROM node:20-alpine AS build
WORKDIR /app
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci
COPY frontend/ .
RUN npm run generate
FROM nginx:alpine
COPY --from=build /app/.output/public /usr/share/nginx/html
COPY docker/nginx/default.prod.conf /etc/nginx/conf.d/default.conf

View file

@ -0,0 +1,25 @@
server {
listen 80;
server_name localhost;
client_max_body_size 10M;
location /api/ {
proxy_pass http://backend:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /sanctum/ {
proxy_pass http://backend:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}

View file

@ -0,0 +1,26 @@
FROM php:8.2-cli-alpine
RUN apk add --no-cache \
curl \
libpng-dev \
libxml2-dev \
zip \
unzip \
git \
sqlite-dev \
&& docker-php-ext-install pdo pdo_mysql pdo_sqlite gd
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
COPY backend/ /var/www/html
COPY docker/php/entrypoint.prod.sh /entrypoint.sh
RUN composer install --no-dev --optimize-autoloader --no-interaction \
&& chmod +x /entrypoint.sh \
&& chown -R www-data:www-data storage bootstrap/cache
EXPOSE 8000
ENTRYPOINT ["/entrypoint.sh"]

View file

@ -0,0 +1,13 @@
#!/bin/sh
set -e
php artisan storage:link || true
php artisan config:cache
php artisan route:cache
php artisan view:cache
if [ "$RUN_MIGRATIONS" = "true" ]; then
php artisan migrate --force
fi
exec php artisan serve --host=0.0.0.0 --port=8000