From a95fac5c0d3001d9df2e4bc45baff7ddc2028b02 Mon Sep 17 00:00:00 2001 From: Tim Stollberg Date: Sat, 18 Jul 2026 14:59:06 +0700 Subject: [PATCH] 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 --- Makefile | 11 ++++++- docker/docker-compose.prod.yml | 55 +++++++++++++++++++++++++++++++++ docker/frontend/Dockerfile.prod | 14 +++++++++ docker/nginx/default.prod.conf | 25 +++++++++++++++ docker/php/Dockerfile.prod | 26 ++++++++++++++++ docker/php/entrypoint.prod.sh | 13 ++++++++ 6 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 docker/docker-compose.prod.yml create mode 100644 docker/frontend/Dockerfile.prod create mode 100644 docker/nginx/default.prod.conf create mode 100644 docker/php/Dockerfile.prod create mode 100644 docker/php/entrypoint.prod.sh diff --git a/Makefile b/Makefile index b2a3e52..f406e59 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml new file mode 100644 index 0000000..781a17f --- /dev/null +++ b/docker/docker-compose.prod.yml @@ -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 diff --git a/docker/frontend/Dockerfile.prod b/docker/frontend/Dockerfile.prod new file mode 100644 index 0000000..2e57022 --- /dev/null +++ b/docker/frontend/Dockerfile.prod @@ -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 diff --git a/docker/nginx/default.prod.conf b/docker/nginx/default.prod.conf new file mode 100644 index 0000000..b7fbf00 --- /dev/null +++ b/docker/nginx/default.prod.conf @@ -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; + } +} diff --git a/docker/php/Dockerfile.prod b/docker/php/Dockerfile.prod new file mode 100644 index 0000000..ad5a373 --- /dev/null +++ b/docker/php/Dockerfile.prod @@ -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"] diff --git a/docker/php/entrypoint.prod.sh b/docker/php/entrypoint.prod.sh new file mode 100644 index 0000000..ce2d77a --- /dev/null +++ b/docker/php/entrypoint.prod.sh @@ -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