From 260dca0999ce52e430f2985f197897577bc84fb4 Mon Sep 17 00:00:00 2001 From: Snider Date: Sat, 21 Feb 2026 21:14:28 +0000 Subject: [PATCH] refactor: move PHP docker files to core-php, keep only CLI Dockerfile Move Dockerfile.app, Dockerfile.web, nginx/, php/, and docker-compose to core-php where they belong. Promote Dockerfile.core to root Dockerfile. Simplify deploy workflow to only build the CLI image. Co-Authored-By: Virgil --- .forgejo/workflows/deploy.yml | 69 +-------- docker/Dockerfile.core => Dockerfile | 0 docker/Dockerfile.app | 107 -------------- docker/Dockerfile.web | 20 --- docker/docker-compose.prod.yml | 200 --------------------------- docker/nginx/default.conf | 59 -------- docker/nginx/security-headers.conf | 6 - docker/php/opcache.ini | 10 -- docker/php/php-fpm.conf | 22 --- 9 files changed, 3 insertions(+), 490 deletions(-) rename docker/Dockerfile.core => Dockerfile (100%) delete mode 100644 docker/Dockerfile.app delete mode 100644 docker/Dockerfile.web delete mode 100644 docker/docker-compose.prod.yml delete mode 100644 docker/nginx/default.conf delete mode 100644 docker/nginx/security-headers.conf delete mode 100644 docker/php/opcache.ini delete mode 100644 docker/php/php-fpm.conf diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml index 3efb3060..dcd9098c 100644 --- a/.forgejo/workflows/deploy.yml +++ b/.forgejo/workflows/deploy.yml @@ -1,5 +1,4 @@ -# Host UK Production Deployment Pipeline -# Builds 3 Docker images via reusable workflow, then triggers Coolify deploy. +# Core CLI Docker image build + push name: Deploy @@ -9,72 +8,10 @@ on: workflow_dispatch: jobs: - test: - name: Test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.3" - extensions: bcmath, gd, intl, mbstring, pdo_mysql, redis, zip - coverage: none - - - name: Install Composer dependencies - run: composer install --no-interaction --prefer-dist - - - name: Run tests - run: composer test - - - name: Check code style - run: ./vendor/bin/pint --test - - build-app: - name: Build App Image - needs: test - uses: core/go-devops/.forgejo/workflows/docker-publish.yml@main - with: - image: lthn/app - dockerfile: docker/Dockerfile.app - registry: docker.io - secrets: inherit - - build-web: - name: Build Web Image - needs: test - uses: core/go-devops/.forgejo/workflows/docker-publish.yml@main - with: - image: lthn/web - dockerfile: docker/Dockerfile.web - registry: docker.io - secrets: inherit - - build-core: - name: Build Core Image - needs: test + build: uses: core/go-devops/.forgejo/workflows/docker-publish.yml@main with: image: lthn/core - dockerfile: docker/Dockerfile.core + dockerfile: Dockerfile registry: docker.io secrets: inherit - - deploy: - name: Deploy to Production - needs: [build-app, build-web, build-core] - runs-on: ubuntu-latest - steps: - - name: Trigger Coolify deploy - run: | - curl -s -X POST \ - -H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}" \ - "${{ secrets.COOLIFY_URL }}/api/v1/deploy" \ - -H "Content-Type: application/json" \ - -d '{"uuid": "${{ secrets.COOLIFY_APP_UUID }}", "force": false}' - - - name: Wait for deployment - run: | - echo "Deployment triggered. Coolify will perform rolling restart." - echo "Monitor at: ${{ secrets.COOLIFY_URL }}" diff --git a/docker/Dockerfile.core b/Dockerfile similarity index 100% rename from docker/Dockerfile.core rename to Dockerfile diff --git a/docker/Dockerfile.app b/docker/Dockerfile.app deleted file mode 100644 index a75b3fe7..00000000 --- a/docker/Dockerfile.app +++ /dev/null @@ -1,107 +0,0 @@ -# Host UK — Laravel Application Container -# PHP 8.3-FPM with all extensions required by the federated monorepo -# -# Build: docker build -f docker/Dockerfile.app -t host-uk/app:latest .. -# (run from host-uk/ workspace root, not core/) - -FROM php:8.3-fpm-alpine AS base - -# System dependencies -RUN apk add --no-cache \ - git \ - curl \ - libpng-dev \ - libjpeg-turbo-dev \ - freetype-dev \ - libwebp-dev \ - libzip-dev \ - icu-dev \ - oniguruma-dev \ - libxml2-dev \ - linux-headers \ - $PHPIZE_DEPS - -# PHP extensions -RUN docker-php-ext-configure gd \ - --with-freetype \ - --with-jpeg \ - --with-webp \ - && docker-php-ext-install -j$(nproc) \ - bcmath \ - exif \ - gd \ - intl \ - mbstring \ - opcache \ - pcntl \ - pdo_mysql \ - soap \ - xml \ - zip - -# Redis extension -RUN pecl install redis && docker-php-ext-enable redis - -# Composer -COPY --from=composer:2 /usr/bin/composer /usr/bin/composer - -# PHP configuration -RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" -COPY docker/php/opcache.ini $PHP_INI_DIR/conf.d/opcache.ini -COPY docker/php/php-fpm.conf /usr/local/etc/php-fpm.d/zz-host-uk.conf - -# --- Build stage --- -FROM base AS build - -WORKDIR /app - -# Install dependencies first (cache layer) -COPY composer.json composer.lock ./ -RUN composer install \ - --no-dev \ - --no-scripts \ - --no-autoloader \ - --prefer-dist \ - --no-interaction - -# Copy application -COPY . . - -# Generate autoloader and run post-install -RUN composer dump-autoload --optimize --no-dev \ - && php artisan package:discover --ansi - -# Build frontend assets -RUN if [ -f package.json ]; then \ - apk add --no-cache nodejs npm && \ - npm ci --production=false && \ - npm run build && \ - rm -rf node_modules; \ - fi - -# --- Production stage --- -FROM base AS production - -WORKDIR /app - -# Copy built application -COPY --from=build /app /app - -# Create storage directories -RUN mkdir -p \ - storage/framework/cache/data \ - storage/framework/sessions \ - storage/framework/views \ - storage/logs \ - bootstrap/cache - -# Permissions -RUN chown -R www-data:www-data storage bootstrap/cache - -# Health check -HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ - CMD php-fpm-healthcheck || exit 1 - -USER www-data - -EXPOSE 9000 diff --git a/docker/Dockerfile.web b/docker/Dockerfile.web deleted file mode 100644 index e2f76c16..00000000 --- a/docker/Dockerfile.web +++ /dev/null @@ -1,20 +0,0 @@ -# Host UK — Nginx Web Server -# Serves static files and proxies PHP to FPM container -# -# Build: docker build -f docker/Dockerfile.web -t host-uk/web:latest . - -FROM nginx:1.27-alpine - -# Copy nginx configuration -COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf -COPY docker/nginx/security-headers.conf /etc/nginx/snippets/security-headers.conf - -# Copy static assets from app build -# (In production, these are volume-mounted from the app container) -# COPY --from=host-uk/app:latest /app/public /app/public - -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD wget -qO- http://localhost/health || exit 1 - -USER nginx -EXPOSE 80 diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml deleted file mode 100644 index 7f25fa74..00000000 --- a/docker/docker-compose.prod.yml +++ /dev/null @@ -1,200 +0,0 @@ -# Host UK Production Docker Compose -# Deployed to de.host.uk.com and de2.host.uk.com via Coolify -# -# Container topology per app server: -# app - PHP 8.3-FPM (all Laravel modules) -# web - Nginx (static files + FastCGI proxy) -# horizon - Laravel Horizon (queue worker) -# scheduler - Laravel scheduler -# mcp - Go MCP server -# redis - Redis 7 (local cache + sessions) -# galera - MariaDB 11 (Galera cluster node) - -services: - app: - image: ${REGISTRY:-gitea.snider.dev}/host-uk/app:${TAG:-latest} - restart: unless-stopped - volumes: - - app-storage:/app/storage - environment: - - APP_ENV=production - - APP_DEBUG=false - - APP_URL=${APP_URL:-https://host.uk.com} - - DB_HOST=galera - - DB_PORT=3306 - - DB_DATABASE=${DB_DATABASE:-hostuk} - - DB_USERNAME=${DB_USERNAME:-hostuk} - - DB_PASSWORD=${DB_PASSWORD} - - REDIS_HOST=redis - - REDIS_PORT=6379 - - CACHE_DRIVER=redis - - SESSION_DRIVER=redis - - QUEUE_CONNECTION=redis - depends_on: - redis: - condition: service_healthy - galera: - condition: service_healthy - healthcheck: - test: ["CMD-SHELL", "php-fpm-healthcheck || exit 1"] - interval: 30s - timeout: 3s - start_period: 10s - retries: 3 - networks: - - app-net - - web: - image: ${REGISTRY:-gitea.snider.dev}/host-uk/web:${TAG:-latest} - restart: unless-stopped - ports: - - "${WEB_PORT:-80}:80" - volumes: - - app-storage:/app/storage:ro - depends_on: - app: - condition: service_healthy - healthcheck: - test: ["CMD", "wget", "-qO-", "http://localhost/health"] - interval: 30s - timeout: 3s - start_period: 5s - retries: 3 - networks: - - app-net - - horizon: - image: ${REGISTRY:-gitea.snider.dev}/host-uk/app:${TAG:-latest} - restart: unless-stopped - command: php artisan horizon - volumes: - - app-storage:/app/storage - environment: - - APP_ENV=production - - DB_HOST=galera - - DB_PORT=3306 - - DB_DATABASE=${DB_DATABASE:-hostuk} - - DB_USERNAME=${DB_USERNAME:-hostuk} - - DB_PASSWORD=${DB_PASSWORD} - - REDIS_HOST=redis - - REDIS_PORT=6379 - depends_on: - app: - condition: service_healthy - healthcheck: - test: ["CMD-SHELL", "php artisan horizon:status | grep -q running"] - interval: 60s - timeout: 5s - start_period: 30s - retries: 3 - networks: - - app-net - - scheduler: - image: ${REGISTRY:-gitea.snider.dev}/host-uk/app:${TAG:-latest} - restart: unless-stopped - command: php artisan schedule:work - volumes: - - app-storage:/app/storage - environment: - - APP_ENV=production - - DB_HOST=galera - - DB_PORT=3306 - - DB_DATABASE=${DB_DATABASE:-hostuk} - - DB_USERNAME=${DB_USERNAME:-hostuk} - - DB_PASSWORD=${DB_PASSWORD} - - REDIS_HOST=redis - - REDIS_PORT=6379 - depends_on: - app: - condition: service_healthy - networks: - - app-net - - mcp: - image: ${REGISTRY:-gitea.snider.dev}/host-uk/core:${TAG:-latest} - restart: unless-stopped - command: core mcp serve - ports: - - "${MCP_PORT:-9001}:9000" - environment: - - MCP_ADDR=:9000 - healthcheck: - test: ["CMD-SHELL", "nc -z localhost 9000 || exit 1"] - interval: 30s - timeout: 3s - retries: 3 - networks: - - app-net - - redis: - image: redis:7-alpine - restart: unless-stopped - command: > - redis-server - --maxmemory 512mb - --maxmemory-policy allkeys-lru - --appendonly yes - --appendfsync everysec - volumes: - - redis-data:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 3s - retries: 5 - networks: - - app-net - - galera: - image: mariadb:11 - restart: unless-stopped - environment: - - MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD} - - MARIADB_DATABASE=${DB_DATABASE:-hostuk} - - MARIADB_USER=${DB_USERNAME:-hostuk} - - MARIADB_PASSWORD=${DB_PASSWORD} - - WSREP_CLUSTER_NAME=hostuk-galera - - WSREP_CLUSTER_ADDRESS=${GALERA_CLUSTER_ADDRESS:-gcomm://} - - WSREP_NODE_ADDRESS=${GALERA_NODE_ADDRESS} - - WSREP_NODE_NAME=${GALERA_NODE_NAME} - - WSREP_SST_METHOD=mariabackup - command: > - --wsrep-on=ON - --wsrep-provider=/usr/lib/galera/libgalera_smm.so - --wsrep-cluster-name=hostuk-galera - --wsrep-cluster-address=${GALERA_CLUSTER_ADDRESS:-gcomm://} - --wsrep-node-address=${GALERA_NODE_ADDRESS} - --wsrep-node-name=${GALERA_NODE_NAME} - --wsrep-sst-method=mariabackup - --binlog-format=ROW - --default-storage-engine=InnoDB - --innodb-autoinc-lock-mode=2 - --innodb-buffer-pool-size=1G - --innodb-log-file-size=256M - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci - volumes: - - galera-data:/var/lib/mysql - ports: - - "${GALERA_PORT:-3306}:3306" - - "4567:4567" - - "4568:4568" - - "4444:4444" - healthcheck: - test: ["CMD-SHELL", "mariadb -u root -p${DB_ROOT_PASSWORD} -e 'SHOW STATUS LIKE \"wsrep_ready\"' | grep -q ON"] - interval: 30s - timeout: 10s - start_period: 60s - retries: 5 - networks: - - app-net - -volumes: - app-storage: - redis-data: - galera-data: - -networks: - app-net: - driver: bridge diff --git a/docker/nginx/default.conf b/docker/nginx/default.conf deleted file mode 100644 index b05018e4..00000000 --- a/docker/nginx/default.conf +++ /dev/null @@ -1,59 +0,0 @@ -# Host UK Nginx Configuration -# Proxies PHP to the app (FPM) container, serves static files directly - -server { - listen 80; - server_name _; - - root /app/public; - index index.php; - - charset utf-8; - - # Security headers - include /etc/nginx/snippets/security-headers.conf; - - # Health check endpoint (no logging) - location = /health { - access_log off; - try_files $uri /index.php?$query_string; - } - - # Static file caching - location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|webp|avif)$ { - expires 1y; - add_header Cache-Control "public, immutable"; - access_log off; - try_files $uri =404; - } - - # Laravel application - location / { - try_files $uri $uri/ /index.php?$query_string; - } - - # PHP-FPM upstream - location ~ \.php$ { - fastcgi_pass app:9000; - fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; - include fastcgi_params; - - fastcgi_hide_header X-Powered-By; - fastcgi_buffer_size 32k; - fastcgi_buffers 16 16k; - fastcgi_read_timeout 300; - - # Pass real client IP from LB proxy protocol - fastcgi_param REMOTE_ADDR $http_x_forwarded_for; - } - - # Block dotfiles (except .well-known) - location ~ /\.(?!well-known) { - deny all; - } - - # Block access to sensitive files - location ~* \.(env|log|yaml|yml|toml|lock|bak|sql)$ { - deny all; - } -} diff --git a/docker/nginx/security-headers.conf b/docker/nginx/security-headers.conf deleted file mode 100644 index 3917d7a2..00000000 --- a/docker/nginx/security-headers.conf +++ /dev/null @@ -1,6 +0,0 @@ -# Security headers for Host UK -add_header X-Frame-Options "SAMEORIGIN" always; -add_header X-Content-Type-Options "nosniff" always; -add_header X-XSS-Protection "1; mode=block" always; -add_header Referrer-Policy "strict-origin-when-cross-origin" always; -add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always; diff --git a/docker/php/opcache.ini b/docker/php/opcache.ini deleted file mode 100644 index 61a65c18..00000000 --- a/docker/php/opcache.ini +++ /dev/null @@ -1,10 +0,0 @@ -; OPcache configuration for production -opcache.enable=1 -opcache.memory_consumption=256 -opcache.interned_strings_buffer=16 -opcache.max_accelerated_files=20000 -opcache.validate_timestamps=0 -opcache.save_comments=1 -opcache.fast_shutdown=1 -opcache.jit_buffer_size=128M -opcache.jit=1255 diff --git a/docker/php/php-fpm.conf b/docker/php/php-fpm.conf deleted file mode 100644 index c19e21c9..00000000 --- a/docker/php/php-fpm.conf +++ /dev/null @@ -1,22 +0,0 @@ -; Host UK PHP-FPM pool configuration -[www] -pm = dynamic -pm.max_children = 50 -pm.start_servers = 10 -pm.min_spare_servers = 5 -pm.max_spare_servers = 20 -pm.max_requests = 1000 -pm.process_idle_timeout = 10s - -; Status page for health checks -pm.status_path = /fpm-status -ping.path = /fpm-ping -ping.response = pong - -; Logging -access.log = /proc/self/fd/2 -slowlog = /proc/self/fd/2 -request_slowlog_timeout = 5s - -; Security -security.limit_extensions = .php