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}
|
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:
|
services:
|
||||||
nginx:
|
nginx:
|
||||||
build: ./docker/nginx
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: nginx
|
||||||
image: ${STACK_NAME:-clupilot}/nginx:latest
|
image: ${STACK_NAME:-clupilot}/nginx:latest
|
||||||
container_name: ${STACK_NAME:-clupilot}-nginx
|
container_name: ${STACK_NAME:-clupilot}-nginx
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "${HTTP_PORT:-80}:80"
|
- "${HTTP_PORT:-80}:80"
|
||||||
volumes:
|
|
||||||
- ./app:/var/www/html
|
|
||||||
extra_hosts:
|
|
||||||
- "host.docker.internal:host-gateway"
|
|
||||||
depends_on:
|
depends_on:
|
||||||
app:
|
app:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
|
|
@ -25,12 +61,13 @@ services:
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
app:
|
app:
|
||||||
build: ./docker/php
|
build: *php-build
|
||||||
image: ${STACK_NAME:-clupilot}/php:latest
|
image: ${STACK_NAME:-clupilot}/php:latest
|
||||||
container_name: ${STACK_NAME:-clupilot}-app
|
container_name: ${STACK_NAME:-clupilot}-app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
environment: *php-env
|
||||||
volumes:
|
volumes:
|
||||||
- ./app:/var/www/html
|
- app-storage:/var/www/html/storage
|
||||||
depends_on:
|
depends_on:
|
||||||
mysql:
|
mysql:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
@ -39,37 +76,51 @@ services:
|
||||||
networks: [clu]
|
networks: [clu]
|
||||||
|
|
||||||
queue:
|
queue:
|
||||||
|
build: *php-build
|
||||||
image: ${STACK_NAME:-clupilot}/php:latest
|
image: ${STACK_NAME:-clupilot}/php:latest
|
||||||
container_name: ${STACK_NAME:-clupilot}-queue
|
container_name: ${STACK_NAME:-clupilot}-queue
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: php artisan queue:work --tries=3 --timeout=120
|
command: php artisan queue:work --tries=3 --timeout=120
|
||||||
|
environment: *php-env
|
||||||
volumes:
|
volumes:
|
||||||
- ./app:/var/www/html
|
- app-storage:/var/www/html/storage
|
||||||
depends_on:
|
depends_on:
|
||||||
|
mysql:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
app:
|
app:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
networks: [clu]
|
networks: [clu]
|
||||||
|
|
||||||
scheduler:
|
scheduler:
|
||||||
|
build: *php-build
|
||||||
image: ${STACK_NAME:-clupilot}/php:latest
|
image: ${STACK_NAME:-clupilot}/php:latest
|
||||||
container_name: ${STACK_NAME:-clupilot}-scheduler
|
container_name: ${STACK_NAME:-clupilot}-scheduler
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: php artisan schedule:work
|
command: php artisan schedule:work
|
||||||
|
environment: *php-env
|
||||||
volumes:
|
volumes:
|
||||||
- ./app:/var/www/html
|
- app-storage:/var/www/html/storage
|
||||||
depends_on:
|
depends_on:
|
||||||
|
mysql:
|
||||||
|
condition: service_healthy
|
||||||
app:
|
app:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
networks: [clu]
|
networks: [clu]
|
||||||
|
|
||||||
reverb:
|
reverb:
|
||||||
|
build: *php-build
|
||||||
image: ${STACK_NAME:-clupilot}/php:latest
|
image: ${STACK_NAME:-clupilot}/php:latest
|
||||||
container_name: ${STACK_NAME:-clupilot}-reverb
|
container_name: ${STACK_NAME:-clupilot}-reverb
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: php artisan reverb:start --host=0.0.0.0 --port=8080
|
command: php artisan reverb:start --host=0.0.0.0 --port=8080
|
||||||
|
environment: *php-env
|
||||||
volumes:
|
volumes:
|
||||||
- ./app:/var/www/html
|
- app-storage:/var/www/html/storage
|
||||||
depends_on:
|
depends_on:
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
app:
|
app:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
networks: [clu]
|
networks: [clu]
|
||||||
|
|
@ -118,6 +169,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
mysql-data:
|
mysql-data:
|
||||||
redis-data:
|
redis-data:
|
||||||
|
app-storage:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
clu:
|
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
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ -d /var/www/html/storage ]; then
|
cd /var/www/html
|
||||||
chmod -R 777 /var/www/html/storage /var/www/html/bootstrap/cache 2>/dev/null || true
|
|
||||||
|
# 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
|
fi
|
||||||
|
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,34 @@
|
||||||
# Portainer Stack environment variables.
|
# 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
|
STACK_NAME=clupilot
|
||||||
|
|
||||||
# Host port mappings
|
|
||||||
HTTP_PORT=80
|
HTTP_PORT=80
|
||||||
MAILPIT_UI_PORT=8025
|
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_DATABASE=clupilot
|
||||||
DB_USERNAME=clupilot
|
DB_USERNAME=clupilot
|
||||||
DB_PASSWORD=clupilot
|
DB_PASSWORD=change-me
|
||||||
DB_ROOT_PASSWORD=rYHIlYyiC8jNy167votk8vhvUmXy
|
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