feat: add Docker infrastructure from CLI
Some checks failed
Code Style / Laravel Pint (push) Failing after 2s
Code Style / PHP CodeSniffer (push) Failing after 1s
Static Analysis / PHPStan (push) Failing after 1s
Static Analysis / Psalm (push) Failing after 2s
Static Analysis / Security Audit (push) Failing after 1s
Static Analysis / PHP Syntax Check (push) Failing after 1s
Tests / PHP 8.2 - Laravel 11.* (push) Failing after 2s
Tests / PHP 8.3 - Laravel 11.* (push) Failing after 2s
Tests / PHP 8.4 - Laravel 11.* (push) Failing after 2s
Tests / PHP 8.4 - Laravel 12.* (push) Failing after 2s
Tests / PHP 8.3 - Laravel 12.* (push) Failing after 2s
Some checks failed
Code Style / Laravel Pint (push) Failing after 2s
Code Style / PHP CodeSniffer (push) Failing after 1s
Static Analysis / PHPStan (push) Failing after 1s
Static Analysis / Psalm (push) Failing after 2s
Static Analysis / Security Audit (push) Failing after 1s
Static Analysis / PHP Syntax Check (push) Failing after 1s
Tests / PHP 8.2 - Laravel 11.* (push) Failing after 2s
Tests / PHP 8.3 - Laravel 11.* (push) Failing after 2s
Tests / PHP 8.4 - Laravel 11.* (push) Failing after 2s
Tests / PHP 8.4 - Laravel 12.* (push) Failing after 2s
Tests / PHP 8.3 - Laravel 12.* (push) Failing after 2s
Move production Docker files from core/cli to their proper home: - Dockerfile.app: PHP 8.3-FPM with Laravel extensions - Dockerfile.web: nginx reverse proxy - docker-compose.prod.yml: full stack (app, web, horizon, scheduler, mcp, redis, galera) - nginx/: default.conf + security-headers.conf - php/: opcache.ini + php-fpm.conf Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
ee97744409
commit
05f03a1ca1
7 changed files with 424 additions and 0 deletions
107
docker/Dockerfile.app
Normal file
107
docker/Dockerfile.app
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# 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
|
||||
20
docker/Dockerfile.web
Normal file
20
docker/Dockerfile.web
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# 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
|
||||
200
docker/docker-compose.prod.yml
Normal file
200
docker/docker-compose.prod.yml
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
# 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
|
||||
59
docker/nginx/default.conf
Normal file
59
docker/nginx/default.conf
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# 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;
|
||||
}
|
||||
}
|
||||
6
docker/nginx/security-headers.conf
Normal file
6
docker/nginx/security-headers.conf
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# 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;
|
||||
10
docker/php/opcache.ini
Normal file
10
docker/php/opcache.ini
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
; 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
|
||||
22
docker/php/php-fpm.conf
Normal file
22
docker/php/php-fpm.conf
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
; 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
|
||||
Loading…
Add table
Reference in a new issue