Deploying with Docker
Deploying with Docker
Docker provides a consistent, portable way to deploy Hazaar Framework applications across different environments. By containerizing your application, you ensure it runs identically in development, staging, and production.
Why Docker?
- Consistency: Identical environments across all stages of deployment
- Isolation: Application dependencies are contained and don't conflict with host systems
- Scalability: Easy to scale horizontally with orchestration tools
- Portability: Run anywhere Docker is supported
Prerequisites
Before deploying with Docker, ensure you have:
- Docker installed (Installation Guide)
- A Hazaar application created (see Getting Started)
- Basic understanding of Docker and Dockerfiles
Choosing a Base Image
Hazaar Framework can run on multiple web servers, each with official Docker images:
- FrankenPHP (Recommended):
dunglas/frankenphp- Modern, high-performance with worker mode support - Apache:
php:8.x-apache- Traditional, widely supported - Nginx:
php:8.x-fpm+ separate Nginx container
This guide focuses on FrankenPHP as the recommended option, with examples for Apache and Nginx.
FrankenPHP Deployment (Recommended)
FrankenPHP offers the best performance for Hazaar applications, especially with worker mode enabled.
Basic Dockerfile
Create a Dockerfile in your application root:
FROM dunglas/frankenphp:latest
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
unzip \
&& rm -rf /var/lib/apt/lists/*
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Set working directory
WORKDIR /app
# Copy composer files
COPY composer.json composer.lock ./
# Install PHP dependencies
RUN composer install --no-dev --optimize-autoloader --no-scripts
# Copy application files
COPY . .
# Set proper permissions
RUN chown -R www-data:www-data /app
# Expose port
EXPOSE 80 443
# Start FrankenPHP
CMD ["frankenphp", "run", "--config", "/app/Caddyfile"]Caddyfile for Docker
Create a Caddyfile in your application root:
{
frankenphp
auto_https off
}
:80 {
root * /app/public
encode gzip
php_server
}Build and Run
# Build the image
docker build -t myapp:latest .
# Run the container
docker run -d \
--name myapp \
-p 80:80 \
-e APPLICATION_ENV=production \
myapp:latestYour application is now accessible at http://localhost.
FrankenPHP with Worker Mode
For maximum performance, enable worker mode in your Docker deployment:
Dockerfile with Worker Mode
FROM dunglas/frankenphp:latest
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
unzip \
&& rm -rf /var/lib/apt/lists/*
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Set working directory
WORKDIR /app
# Copy composer files
COPY composer.json composer.lock ./
# Install PHP dependencies
RUN composer install --no-dev --optimize-autoloader --no-scripts
# Copy application files
COPY . .
# Set proper permissions
RUN chown -R www-data:www-data /app
# Configure PHP
RUN echo "opcache.enable=1" >> /usr/local/etc/php/conf.d/opcache.ini && \
echo "opcache.validate_timestamps=0" >> /usr/local/etc/php/conf.d/opcache.ini && \
echo "opcache.memory_consumption=256" >> /usr/local/etc/php/conf.d/opcache.ini
# Expose ports
EXPOSE 80 443
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \
CMD curl -f http://localhost/ || exit 1
# Start FrankenPHP with worker mode
CMD ["frankenphp", "run", "--config", "/app/Caddyfile"]Caddyfile with Worker Mode
{
frankenphp {
num_threads {$CADDY_NUM_THREADS:auto}
worker /app/public/worker.php
}
auto_https off
}
:80 {
root * /app/public
encode gzip
php_server
log {
output stdout
format console
}
}Build and Run with Worker Mode
# Build the image
docker build -t myapp:latest .
# Run with worker mode
docker run -d \
--name myapp \
-p 80:80 \
-e APPLICATION_ENV=production \
-e CADDY_NUM_THREADS=4 \
myapp:latestMulti-Stage Build (Production Optimized)
Use multi-stage builds to create smaller, more efficient production images:
# Stage 1: Build dependencies
FROM composer:latest AS builder
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-scripts --prefer-dist
# Stage 2: Production image
FROM dunglas/frankenphp:latest
# Install runtime dependencies only
RUN apt-get update && apt-get install -y \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy dependencies from builder
COPY --from=builder /app/vendor ./vendor
# Copy application files
COPY . .
# Configure PHP for production
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
echo "opcache.enable=1" >> "$PHP_INI_DIR/conf.d/opcache.ini" && \
echo "opcache.validate_timestamps=0" >> "$PHP_INI_DIR/conf.d/opcache.ini" && \
echo "opcache.memory_consumption=256" >> "$PHP_INI_DIR/conf.d/opcache.ini" && \
echo "opcache.max_accelerated_files=10000" >> "$PHP_INI_DIR/conf.d/opcache.ini"
# Set proper permissions
RUN chown -R www-data:www-data /app
# Expose ports
EXPOSE 80 443
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \
CMD curl -f http://localhost/ || exit 1
CMD ["frankenphp", "run", "--config", "/app/Caddyfile"]Docker Compose
For more complex deployments with databases and other services, use Docker Compose.
Create a docker-compose.yml file:
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "80:80"
- "443:443"
environment:
- APPLICATION_ENV=production
- CADDY_NUM_THREADS=auto
- DB_HOST=database
- DB_NAME=myapp
- DB_USER=myapp
- DB_PASS=secret
depends_on:
database:
condition: service_healthy
volumes:
- ./storage:/app/storage
restart: unless-stopped
networks:
- app-network
database:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=rootsecret
- MYSQL_DATABASE=myapp
- MYSQL_USER=myapp
- MYSQL_PASSWORD=secret
volumes:
- db-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- app-network
volumes:
db-data:
networks:
app-network:
driver: bridgeRun with Docker Compose
# Start all services
docker compose up -d
# View logs
docker compose logs -f app
# Stop all services
docker compose down
# Rebuild and restart
docker compose up -d --buildApache Deployment
If you prefer Apache, here's a basic Dockerfile:
FROM php:8.2-apache
# Enable Apache modules
RUN a2enmod rewrite
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
unzip \
libzip-dev \
&& docker-php-ext-install zip pdo pdo_mysql \
&& rm -rf /var/lib/apt/lists/*
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
# Copy composer files
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader
# Copy application
COPY . .
# Configure Apache DocumentRoot
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' \
/etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' \
/etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
# Set permissions
RUN chown -R www-data:www-data /var/www/html
EXPOSE 80
CMD ["apache2-foreground"]Nginx Deployment
For Nginx, you need both PHP-FPM and Nginx containers:
PHP-FPM Dockerfile
FROM php:8.2-fpm
# Install dependencies
RUN apt-get update && apt-get install -y \
git \
unzip \
libzip-dev \
&& docker-php-ext-install zip pdo pdo_mysql \
&& rm -rf /var/lib/apt/lists/*
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
# Copy and install dependencies
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader
# Copy application
COPY . .
# Set permissions
RUN chown -R www-data:www-data /var/www/html
EXPOSE 9000
CMD ["php-fpm"]Docker Compose with Nginx
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./:/var/www/html
networks:
- app-network
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./:/var/www/html
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
networks:
- app-network
networks:
app-network:nginx.conf
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param APPLICATION_ENV production;
include fastcgi_params;
}
location ~ /\. {
deny all;
}
}Environment Variables
Configure your application using environment variables in Docker:
docker run -d \
-e APPLICATION_ENV=production \
-e DB_HOST=database \
-e DB_NAME=myapp \
-e DB_USER=myapp \
-e DB_PASS=secret \
-e CADDY_NUM_THREADS=4 \
myapp:latestOr in docker-compose.yml:
services:
app:
environment:
- APPLICATION_ENV=production
- DB_HOST=database
- DB_NAME=myapp
- DB_USER=myapp
- DB_PASS=secretYou can also use environment files:
services:
app:
env_file:
- .env.productionProduction Best Practices
1. Use Specific Image Tags
Avoid latest tags in production:
FROM dunglas/frankenphp:1.0.02. Minimize Image Size
- Use multi-stage builds
- Remove development dependencies
- Clean up package manager caches
3. Security
# Run as non-root user
USER www-data
# Don't install unnecessary packages
RUN apt-get install -y --no-install-recommends ...
# Scan images for vulnerabilities4. Health Checks
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \
CMD curl -f http://localhost/ || exit 15. Resource Limits
In docker-compose.yml:
services:
app:
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '1'
memory: 1G6. Logging
services:
app:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"Orchestration
For production deployments at scale, consider using orchestration platforms:
Docker Swarm
# Initialize swarm
docker swarm init
# Deploy stack
docker stack deploy -c docker-compose.yml myapp
# Scale services
docker service scale myapp_app=3Kubernetes
Create a basic deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:latest
ports:
- containerPort: 80
env:
- name: APPLICATION_ENV
value: "production"
resources:
limits:
memory: "2Gi"
cpu: "1000m"Troubleshooting
View Container Logs
# Docker
docker logs -f myapp
# Docker Compose
docker compose logs -f appAccess Container Shell
# Docker
docker exec -it myapp /bin/bash
# Docker Compose
docker compose exec app /bin/bashCheck Container Status
# Docker
docker ps
docker inspect myapp
# Docker Compose
docker compose psCommon Issues
Container exits immediately:
- Check logs:
docker logs myapp - Verify Caddyfile syntax
- Ensure all required files are copied
Permission denied errors:
- Check file ownership:
chown -R www-data:www-data /app - Verify volume mount permissions
Application not accessible:
- Check port mapping:
-p 80:80 - Verify firewall rules
- Check container health:
docker ps
Further Reading
- FrankenPHP Deployment - Detailed FrankenPHP configuration
- Docker Documentation
- Docker Compose Documentation
- FrankenPHP Docker Images