Bake app into multi-stage image; drop bind mounts for Portainer remote endpoint
- New root Dockerfile: composer-deps + node-build + assembled code stages, final PHP-FPM and nginx targets both built from same context. - docker-compose.yml: all services build/use baked images. No ./app or config bind mounts (broke with Portainer on VM-A + Docker daemon on VM-B). - app-storage named volume preserves Laravel storage across redeploys. - Laravel env passed via x-php-env anchor from stack env vars. - Removed per-service docker/php/Dockerfile and docker/nginx/Dockerfile. - entrypoint.sh: touch .env, ensure storage dirs, idempotent storage:link.master
parent
6681df5a10
commit
87f41387ce
|
|
@ -0,0 +1,27 @@
|
|||
.git
|
||||
.gitignore
|
||||
.github
|
||||
templates
|
||||
certs
|
||||
stack.env*
|
||||
docker-compose*.override.yml
|
||||
|
||||
# Old single-target Dockerfiles (now merged into root Dockerfile)
|
||||
docker/php/Dockerfile
|
||||
docker/nginx/Dockerfile
|
||||
|
||||
# Laravel build artifacts / local state
|
||||
app/node_modules
|
||||
app/vendor
|
||||
app/.env
|
||||
app/.env.backup
|
||||
app/.env.production
|
||||
app/public/build
|
||||
app/public/hot
|
||||
app/storage/logs
|
||||
app/storage/framework/cache
|
||||
app/storage/framework/sessions
|
||||
app/storage/framework/views
|
||||
app/bootstrap/cache
|
||||
app/.phpunit.cache
|
||||
app/.phpunit.result.cache
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
# ---------- composer deps ----------
|
||||
FROM composer:2.7 AS composer-deps
|
||||
WORKDIR /build
|
||||
COPY app/composer.json app/composer.lock ./
|
||||
RUN composer install \
|
||||
--no-dev --no-scripts --no-autoloader \
|
||||
--prefer-dist --no-interaction --no-progress
|
||||
|
||||
# ---------- node / vite build ----------
|
||||
FROM node:20-alpine AS node-build
|
||||
WORKDIR /build
|
||||
COPY app/package.json app/package-lock.json ./
|
||||
RUN npm ci --no-audit --no-fund
|
||||
COPY app/ ./
|
||||
RUN npm run build
|
||||
|
||||
# ---------- assembled code ----------
|
||||
FROM alpine:3.20 AS code
|
||||
WORKDIR /var/www/html
|
||||
COPY app/ ./
|
||||
COPY --from=composer-deps /build/vendor ./vendor
|
||||
COPY --from=node-build /build/public/build ./public/build
|
||||
|
||||
# ---------- PHP-FPM runtime ----------
|
||||
FROM php:8.4-fpm-alpine AS php
|
||||
RUN apk add --no-cache \
|
||||
git curl libpng-dev libzip-dev oniguruma-dev icu-dev \
|
||||
autoconf g++ make linux-headers $PHPIZE_DEPS \
|
||||
&& docker-php-ext-install pdo_mysql mbstring zip exif pcntl bcmath gd intl sockets \
|
||||
&& pecl install redis && docker-php-ext-enable redis \
|
||||
&& apk del autoconf g++ make linux-headers $PHPIZE_DEPS \
|
||||
&& rm -rf /tmp/* /var/cache/apk/*
|
||||
|
||||
COPY --from=composer:2.7 /usr/bin/composer /usr/bin/composer
|
||||
|
||||
WORKDIR /var/www/html
|
||||
COPY --from=code /var/www/html /var/www/html
|
||||
|
||||
RUN composer dump-autoload --optimize --no-dev --no-interaction \
|
||||
&& mkdir -p storage/framework/cache storage/framework/sessions storage/framework/views \
|
||||
storage/logs storage/app/public bootstrap/cache \
|
||||
&& chown -R www-data:www-data storage bootstrap/cache \
|
||||
&& chmod -R 775 storage bootstrap/cache
|
||||
|
||||
COPY docker/php/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
CMD ["php-fpm"]
|
||||
|
||||
# ---------- nginx runtime ----------
|
||||
FROM nginx:1.27-alpine AS nginx
|
||||
COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=code /var/www/html/public /var/www/html/public
|
||||
|
|
@ -1,17 +1,53 @@
|
|||
name: ${STACK_NAME:-clupilot}
|
||||
|
||||
x-php-build: &php-build
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: php
|
||||
|
||||
x-php-env: &php-env
|
||||
APP_NAME: ${APP_NAME:-CluPilot}
|
||||
APP_ENV: ${APP_ENV:-production}
|
||||
APP_KEY: ${APP_KEY:?APP_KEY required — run php artisan key:generate locally and paste the value}
|
||||
APP_DEBUG: ${APP_DEBUG:-false}
|
||||
APP_URL: ${APP_URL:-http://localhost}
|
||||
LOG_CHANNEL: stack
|
||||
LOG_LEVEL: ${LOG_LEVEL:-info}
|
||||
DB_CONNECTION: mysql
|
||||
DB_HOST: mysql
|
||||
DB_PORT: 3306
|
||||
DB_DATABASE: ${DB_DATABASE:-clupilot}
|
||||
DB_USERNAME: ${DB_USERNAME:-clupilot}
|
||||
DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD required}
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
CACHE_STORE: redis
|
||||
SESSION_DRIVER: redis
|
||||
QUEUE_CONNECTION: redis
|
||||
BROADCAST_CONNECTION: reverb
|
||||
REVERB_APP_ID: ${REVERB_APP_ID:-clupilot}
|
||||
REVERB_APP_KEY: ${REVERB_APP_KEY:?REVERB_APP_KEY required}
|
||||
REVERB_APP_SECRET: ${REVERB_APP_SECRET:?REVERB_APP_SECRET required}
|
||||
REVERB_HOST: 0.0.0.0
|
||||
REVERB_PORT: 8080
|
||||
REVERB_SCHEME: http
|
||||
MAIL_MAILER: ${MAIL_MAILER:-log}
|
||||
MAIL_HOST: ${MAIL_HOST:-mailpit}
|
||||
MAIL_PORT: ${MAIL_PORT:-1025}
|
||||
MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS:-no-reply@clupilot.local}
|
||||
MAIL_FROM_NAME: ${APP_NAME:-CluPilot}
|
||||
|
||||
services:
|
||||
nginx:
|
||||
build: ./docker/nginx
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: nginx
|
||||
image: ${STACK_NAME:-clupilot}/nginx:latest
|
||||
container_name: ${STACK_NAME:-clupilot}-nginx
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${HTTP_PORT:-80}:80"
|
||||
volumes:
|
||||
- ./app:/var/www/html
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
depends_on:
|
||||
app:
|
||||
condition: service_started
|
||||
|
|
@ -25,12 +61,13 @@ services:
|
|||
retries: 3
|
||||
|
||||
app:
|
||||
build: ./docker/php
|
||||
build: *php-build
|
||||
image: ${STACK_NAME:-clupilot}/php:latest
|
||||
container_name: ${STACK_NAME:-clupilot}-app
|
||||
restart: unless-stopped
|
||||
environment: *php-env
|
||||
volumes:
|
||||
- ./app:/var/www/html
|
||||
- app-storage:/var/www/html/storage
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
|
|
@ -39,37 +76,51 @@ services:
|
|||
networks: [clu]
|
||||
|
||||
queue:
|
||||
build: *php-build
|
||||
image: ${STACK_NAME:-clupilot}/php:latest
|
||||
container_name: ${STACK_NAME:-clupilot}-queue
|
||||
restart: unless-stopped
|
||||
command: php artisan queue:work --tries=3 --timeout=120
|
||||
environment: *php-env
|
||||
volumes:
|
||||
- ./app:/var/www/html
|
||||
- app-storage:/var/www/html/storage
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
app:
|
||||
condition: service_started
|
||||
networks: [clu]
|
||||
|
||||
scheduler:
|
||||
build: *php-build
|
||||
image: ${STACK_NAME:-clupilot}/php:latest
|
||||
container_name: ${STACK_NAME:-clupilot}-scheduler
|
||||
restart: unless-stopped
|
||||
command: php artisan schedule:work
|
||||
environment: *php-env
|
||||
volumes:
|
||||
- ./app:/var/www/html
|
||||
- app-storage:/var/www/html/storage
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
app:
|
||||
condition: service_started
|
||||
networks: [clu]
|
||||
|
||||
reverb:
|
||||
build: *php-build
|
||||
image: ${STACK_NAME:-clupilot}/php:latest
|
||||
container_name: ${STACK_NAME:-clupilot}-reverb
|
||||
restart: unless-stopped
|
||||
command: php artisan reverb:start --host=0.0.0.0 --port=8080
|
||||
environment: *php-env
|
||||
volumes:
|
||||
- ./app:/var/www/html
|
||||
- app-storage:/var/www/html/storage
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
app:
|
||||
condition: service_started
|
||||
networks: [clu]
|
||||
|
|
@ -118,6 +169,7 @@ services:
|
|||
volumes:
|
||||
mysql-data:
|
||||
redis-data:
|
||||
app-storage:
|
||||
|
||||
networks:
|
||||
clu:
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
FROM nginx:1.27-alpine
|
||||
COPY default.conf /etc/nginx/conf.d/default.conf
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
FROM php:8.4-fpm-alpine
|
||||
|
||||
RUN apk add --no-cache \
|
||||
git curl libpng-dev libzip-dev oniguruma-dev icu-dev \
|
||||
autoconf g++ make linux-headers $PHPIZE_DEPS
|
||||
|
||||
RUN docker-php-ext-install \
|
||||
pdo_mysql mbstring zip exif pcntl bcmath gd intl sockets
|
||||
|
||||
RUN pecl install redis && docker-php-ext-enable redis
|
||||
|
||||
COPY --from=composer:2.7 /usr/bin/composer /usr/bin/composer
|
||||
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
CMD ["php-fpm"]
|
||||
|
|
@ -1,8 +1,20 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ -d /var/www/html/storage ]; then
|
||||
chmod -R 777 /var/www/html/storage /var/www/html/bootstrap/cache 2>/dev/null || true
|
||||
cd /var/www/html
|
||||
|
||||
# Laravel needs a .env file to exist; values come from container env.
|
||||
[ -f .env ] || : > .env
|
||||
|
||||
# Storage volume mounts can come up empty — ensure tree exists + writable.
|
||||
mkdir -p storage/framework/cache storage/framework/sessions storage/framework/views \
|
||||
storage/logs storage/app/public bootstrap/cache
|
||||
chmod -R 775 storage bootstrap/cache 2>/dev/null || true
|
||||
chown -R www-data:www-data storage bootstrap/cache 2>/dev/null || true
|
||||
|
||||
# Public storage symlink (idempotent).
|
||||
if [ ! -L public/storage ] && [ -d storage/app/public ]; then
|
||||
php artisan storage:link 2>/dev/null || true
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
|
|
|
|||
|
|
@ -1,14 +1,34 @@
|
|||
# Portainer Stack environment variables.
|
||||
# In Portainer: Stack → Environment variables → paste these (set real secrets).
|
||||
# Paste these into Portainer → Stack → Environment variables (Advanced mode),
|
||||
# then fill in real secrets. Required ones will fail deploy if missing.
|
||||
|
||||
# ---- Stack ----
|
||||
STACK_NAME=clupilot
|
||||
|
||||
# Host port mappings
|
||||
HTTP_PORT=80
|
||||
MAILPIT_UI_PORT=8025
|
||||
|
||||
# Database (MariaDB)
|
||||
# ---- Laravel app ----
|
||||
APP_NAME=CluPilot
|
||||
APP_ENV=production
|
||||
# Generate once locally: cd app && php artisan key:generate --show
|
||||
APP_KEY=base64:REPLACE_ME
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://localhost
|
||||
LOG_LEVEL=info
|
||||
|
||||
# ---- Database (MariaDB) ----
|
||||
DB_DATABASE=clupilot
|
||||
DB_USERNAME=clupilot
|
||||
DB_PASSWORD=clupilot
|
||||
DB_ROOT_PASSWORD=rYHIlYyiC8jNy167votk8vhvUmXy
|
||||
DB_PASSWORD=change-me
|
||||
DB_ROOT_PASSWORD=change-me-root
|
||||
|
||||
# ---- Reverb (websockets) ----
|
||||
REVERB_APP_ID=clupilot
|
||||
REVERB_APP_KEY=change-me-key
|
||||
REVERB_APP_SECRET=change-me-secret
|
||||
|
||||
# ---- Mail ----
|
||||
MAIL_MAILER=log
|
||||
MAIL_HOST=mailpit
|
||||
MAIL_PORT=1025
|
||||
MAIL_FROM_ADDRESS=no-reply@clupilot.local
|
||||
|
|
|
|||
Loading…
Reference in New Issue