Docker Deployment
Deploy SaasKitFy with Docker using a multi-container setup: backend (PHP + Nginx), frontend (static files via Nginx), MySQL, and Redis.
Backend Dockerfile
Create backend/Dockerfile:
FROM php:8.2-fpm-alpine
# Install extensions
RUN apk add --no-cache \
libpq-dev redis libzip-dev icu-dev \
&& docker-php-ext-install pdo pdo_pgsql pdo_mysql zip intl opcache \
&& pecl install redis && docker-php-ext-enable redis
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
# Install dependencies first (cache layer)
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader
# Copy application code
COPY . .
RUN composer dump-autoload --optimize
# Set permissions
RUN chown -R www-data:www-data storage bootstrap/cache \
&& chmod -R 775 storage bootstrap/cache
EXPOSE 9000
CMD ["php-fpm"]
Frontend Build
Build the frontend locally or in CI, then serve the static output:
cd frontend
npm install
npm run build
# Output is in frontend/dist/
Nginx Configuration
Create docker/nginx/default.conf to proxy PHP and serve the frontend:
# Backend API
server {
listen 80;
server_name api.yourdomain.com;
root /var/www/html/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass backend:9000;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}
# Frontend SPA
server {
listen 80;
server_name app.yourdomain.com;
root /var/www/frontend;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
Docker Compose
Create docker-compose.yml in the project root:
version: "3.8"
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile
volumes:
- backend-storage:/var/www/html/storage
env_file:
- ./backend/.env
depends_on:
- mysql
- redis
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./backend/public:/var/www/html/public:ro
- ./frontend/dist:/var/www/frontend:ro
- ./docker/ssl:/etc/nginx/ssl:ro
depends_on:
- backend
restart: unless-stopped
queue-worker:
build:
context: ./backend
dockerfile: Dockerfile
command: php artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
env_file:
- ./backend/.env
depends_on:
- mysql
- redis
restart: unless-stopped
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_USER: ${DB_USERNAME}
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
restart: unless-stopped
volumes:
backend-storage:
mysql-data:
redis-data:
Running
# Build and start all services
docker compose up -d --build
# Run migrations
docker compose exec backend php artisan migrate --force
# Cache config and routes
docker compose exec backend php artisan config:cache
docker compose exec backend php artisan route:cache
docker compose exec backend php artisan view:cache
# Create storage symlink
docker compose exec backend php artisan storage:link
Using PostgreSQL Instead
Replace the mysql service with:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: ${DB_DATABASE}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
restart: unless-stopped
Update your .env to set DB_CONNECTION=pgsql and DB_HOST=postgres.