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.