Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
strategy:
matrix:
operating-system: [ ubuntu-latest ]
php-versions: [ '8.1', '8.2' ]
php-versions: [ '8.2', '8.3' ]
name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }}

steps:
Expand Down
119 changes: 119 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
FROM php:8.4-fpm-alpine AS php

# Install PHP extensions and system dependencies
RUN apk add --no-cache \
nginx \
supervisor \
nodejs \
npm \
libzip-dev \
libpng-dev \
libjpeg-turbo-dev \
libwebp-dev \
libxpm-dev \
freetype-dev \
imagemagick \
imagemagick-dev \
oniguruma-dev \
icu-dev \
linux-headers \
bash \
curl \
git \
certbot \
certbot-nginx \
postgresql-dev

RUN docker-php-ext-configure gd \
--with-freetype \
--with-jpeg \
--with-webp

# Install core PHP extensions
RUN docker-php-ext-install -j$(nproc) \
bcmath \
mbstring \
gd \
pcntl \
intl \
pdo_mysql \
pdo_pgsql \
pgsql \
exif \
sockets \
opcache \
zip \
ftp

# Install Redis extension via PECL
RUN apk add --no-cache --virtual .build-deps $PHPIZE_DEPS \
&& pecl install redis \
&& docker-php-ext-enable redis \
&& apk del .build-deps

# Copy application code
WORKDIR /var/www/html
COPY . .

# Composer install (if composer.lock present)
RUN if [ -f composer.lock ]; then \
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer && \
composer install --no-dev --no-scripts --no-interaction --no-progress --optimize-autoloader; \
fi

# Nginx config
RUN mkdir -p /etc/nginx/conf.d /var/run/nginx
COPY system/nginx/nginx.conf /etc/nginx/nginx.conf
COPY system/nginx/nginx-site.conf /etc/nginx/conf.d/default.conf

# PHP config
RUN mkdir -p /var/log/php /var/lib/php/sessions && \
chown -R www-data:www-data /var/log/php /var/lib/php/sessions
COPY system/php.ini /usr/local/etc/php/conf.d/99-app.ini

# Create log directories (supervisor uses /var/log/supervisor)
RUN mkdir -p /var/log/supervisor /var/log/cron

# Copy supervisor configuration
COPY system/nginx/supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# Crontab (busybox crond reads /etc/crontabs/root for the root user)
COPY system/crontab /etc/crontabs/root
RUN chmod 0600 /etc/crontabs/root

# Certbot (Let's Encrypt) setup
RUN mkdir -p /etc/letsencrypt /var/lib/letsencrypt /var/log/letsencrypt && \
chmod 700 /etc/letsencrypt

# Validate nginx config
RUN nginx -t

# Environment variables
ENV SKIP_COMPOSER=1 \
PHP_ERRORS_STDERR=1 \
RUN_SCRIPTS=1 \
REAL_IP_HEADER=1 \
PHP_MEM_LIMIT=1024 \
PHP_POST_MAX_SIZE=250 \
PHP_UPLOAD_MAX_FILESIZE=250 \
APP_ENV=production \
APP_DEBUG=false \
LOG_CHANNEL=stderr \
COMPOSER_ALLOW_SUPERUSER=1 \
SUPERVISOR_LOG_LEVEL=info

# Define public volume
VOLUME /var/www/html/public

# Expose HTTP and HTTPS ports
EXPOSE 80 443

# Entrypoint script to start Supervisor, Nginx, and PHP-FPM
COPY system/nginx/start.sh /start.sh
RUN chmod +x /start.sh

# Download MinIO client
RUN curl -o /var/www/html/mc https://dl.min.io/client/mc/release/linux-amd64/archive/mc \
&& chmod +x /var/www/html/mc

CMD ["/start.sh"]
118 changes: 118 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
services:
mysql:
image: mysql:8
container_name: bowphp-mysql
restart: unless-stopped
environment:
MYSQL_DATABASE: bowphp
MYSQL_USER: bowphp
MYSQL_PASSWORD: bowphp
MYSQL_ROOT_PASSWORD: bowphp
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
networks:
- bowphp-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-ubowphp", "-pbowphp"]
interval: 10s
timeout: 5s
retries: 5

postgres:
image: postgres:16-alpine
container_name: bowphp-postgres
restart: unless-stopped
environment:
POSTGRES_DB: bowphp
POSTGRES_USER: bowphp
POSTGRES_PASSWORD: bowphp
ports:
- "5433:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- bowphp-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U bowphp -d bowphp"]
interval: 10s
timeout: 5s
retries: 5

adminer:
image: adminer:latest
container_name: bowphp-adminer
restart: unless-stopped
ports:
- "8080:8080"
environment:
ADMINER_DEFAULT_SERVER: postgres
networks:
- bowphp-network
depends_on:
- mysql
- postgres

redis:
image: redis:7-alpine
container_name: bowphp-redis
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- bowphp-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5

beanstalkd:
image: schickling/beanstalkd:latest
container_name: bowphp-beanstalkd
restart: unless-stopped
ports:
- "11300:11300"
volumes:
- beanstalkd_data:/var/lib/beanstalkd
networks:
- bowphp-network
healthcheck:
test: ["CMD-SHELL", "echo 'stats' | nc -w 1 localhost 11300 | grep -q current-jobs-ready"]
interval: 10s
timeout: 5s
retries: 5

rabbitmq:
image: rabbitmq:3-management-alpine
container_name: bowphp-rabbitmq
restart: unless-stopped
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
ports:
- "5672:5672"
- "15672:15672"
volumes:
- rabbitmq_data:/var/lib/rabbitmq
networks:
- bowphp-network
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "ping"]
interval: 10s
timeout: 5s
retries: 5

volumes:
mysql_data:
postgres_data:
redis_data:
beanstalkd_data:
rabbitmq_data:

networks:
bowphp-network:
driver: bridge
1 change: 1 addition & 0 deletions system/crontab
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#
71 changes: 71 additions & 0 deletions system/frankenphp/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
admin off
auto_https off
skip_install_trust
persist_config off
}

:80 {
# Health check endpoint
@health path /health
respond @health "OK" 200

log {
level {$LOG_LEVEL:WARN}
format json
output stdout
}

root * /app/public

encode zstd br gzip

header {
-Server
-X-Powered-By
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=()"
}

@blocked {
path *.env* *.ini *.conf *.sql *.bak *.orig *.old *.git* *.lock .htaccess
path /vendor/* /node_modules/* /.git/* /storage/* /var/*
path composer.* package*.json *.yml *.yaml
}
respond @blocked 403

@static {
file
path *.js *.css *.jpg *.jpeg *.png *.gif *.ico *.svg *.woff *.woff2 *.ttf *.eot *.webp *.avif *.map
}
handle @static {
header Cache-Control "public, max-age=31536000, immutable"
file_server
}

@media {
file
path *.mp4 *.webm *.mp3 *.ogg *.wav *.pdf *.zip *.tar *.gz
}
handle @media {
header Cache-Control "public, max-age=86400"
file_server
}

php_server {
root /app/public
index index.php
}

handle_errors {
@404 expression {http.error.status_code} == 404
respond @404 "404 Not Found" 404

@5xx expression {http.error.status_code} >= 500
respond @5xx "Server Error" {http.error.status_code}

respond "{http.error.status_code} {http.error.status_text}"
}
}
22 changes: 22 additions & 0 deletions system/frankenphp/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash
set -e

echo "Starting Papac & Co Application..."

# Create required directories
mkdir -p /app/var/cache /app/var/logs /app/var/sessions /app/var/storage

# Ensure .env.json exists
if [ ! -f /app/.env.json ]; then
echo '{}' > /app/.env.json
fi

# Run migrations
echo "Running migrations..."
php bow migrate 2>&1 || {
echo "Migration command failed. Continuing..."
}

# Start supervisor and wait
echo "Starting supervisord..."
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
54 changes: 54 additions & 0 deletions system/frankenphp/supervisord.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
[supervisord]
nodaemon=true
user=root
logfile=/app/var/logs/supervisord.log
pidfile=/app/var/supervisord.pid
childlogdir=/app/var/logs
loglevel=info

[program:crond]
command=/usr/sbin/crond -f -l 8
autostart=true
autorestart=true
startsecs=5
startretries=3
stderr_logfile=/app/var/logs/crond.log
stdout_logfile=/app/var/logs/crond.log
stdout_logfile_maxbytes=10MB
stderr_logfile_maxbytes=10MB

[program:frankenphp]
command=/usr/local/bin/frankenphp run --config /etc/caddy/Caddyfile
user=root
autostart=true
autorestart=true
startretries=3
stdout_logfile=/app/var/logs/frankenphp.log
stderr_logfile=/app/var/logs/frankenphp_error.log
stdout_logfile_maxbytes=10MB
stderr_logfile_maxbytes=10MB
priority=1

[program:beanstalkd]
command=/usr/bin/beanstalkd -l 0.0.0.0 -p 11300
user=root
autostart=true
autorestart=true
startretries=3
stdout_logfile=/app/var/logs/beanstalkd.log
stderr_logfile=/app/var/logs/beanstalkd_error.log
stdout_logfile_maxbytes=10MB
stderr_logfile_maxbytes=10MB
priority=2

[program:microservice]
directory=/var/www/html
command=php bow microservice:listen
autostart=true
autorestart=true
startsecs=5
startretries=3
stderr_logfile=/var/log/supervisor/microservice_worker.log
stdout_logfile=/var/log/supervisor/microservice_worker.log
stdout_logfile_maxbytes=10MB
stderr_logfile_maxbytes=10MB
Loading