📋 Complete Laravel Docker Setup Guide
🗂️ Project Structure your-laravel-project/ ├── .dockerignore ├── Dockerfile ├── docker-compose.yml # Dev/QA ├── docker-compose.prod.yml # Production (local storage) ├── docker-compose.prod-s3.yml # Production (with S3) ├── .env.example ├── .env # Local dev (gitignored) ├── .github/ │ └── workflows/ │ ├── deploy-qa.yml │ └── deploy-prod.yml ├── docker/ │ ├── nginx/ │ │ ├── default.conf # Dev/QA nginx config │ │ └── prod.conf # Production nginx config │ └── scripts/ │ ├── deploy-qa.sh │ └── deploy-prod.sh ├── app/ ├── config/ ├── public/ ├── storage/ └── ... (other Laravel files)
📄 1. .dockerignore vendor/ node_modules/ storage/logs/* storage/framework/cache/* storage/framework/sessions/* storage/framework/views/* !storage/app/.gitignore !storage/framework/.gitignore !storage/logs/.gitignore .env .env.* .git .gitignore .gitattributes docker-compose*.yml Dockerfile *.md tests/ .phpunit.result.cache
📄 2. Dockerfile (Shared for Dev/QA and Prod) dockerfileFROM php:8.3-fpm
RUN usermod -u 1000 www-data && groupmod -g 1000 www-data
WORKDIR /var/www
RUN apt-get update && apt-get install -y
git
curl
libpng-dev
libjpeg-dev
libfreetype6-dev
libonig-dev
libxml2-dev
zip
unzip
libzip-dev
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN docker-php-ext-configure gd --with-freetype --with-jpeg
&& docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip
RUN echo "upload_max_filesize = 100M" >> /usr/local/etc/php/conf.d/uploads.ini
&& echo "post_max_size = 100M" >> /usr/local/etc/php/conf.d/uploads.ini
&& echo "memory_limit = 512M" >> /usr/local/etc/php/conf.d/uploads.ini
&& echo "max_execution_time = 300" >> /usr/local/etc/php/conf.d/uploads.ini
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoload --optimize-autoloader --prefer-dist
COPY --chown=www-data:www-data . .
RUN composer dump-autoload --optimize
RUN chmod -R 775 /var/www/storage /var/www/bootstrap/cache
USER www-data
EXPOSE 9000
CMD ["php-fpm"]
📄 3. docker-compose.yml (Dev/QA Environment) yamlversion: '3.8'
services: app: build: context: . dockerfile: Dockerfile container_name: laravel-app-dev restart: unless-stopped working_dir: /var/www volumes: - ./:/var/www - vendor_volume:/var/www/vendor # Keep vendor in container networks: - laravel-dev depends_on: - mysql
nginx: image: nginx:alpine container_name: laravel-nginx-dev restart: unless-stopped ports: - "8080:80" volumes: - ./:/var/www - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf networks: - laravel-dev depends_on: - app
mysql:
image: mysql:8.0
container_name: laravel-mysql-dev
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD:
phpmyadmin: image: phpmyadmin:latest container_name: laravel-phpmyadmin-dev restart: unless-stopped ports: - "8081:80" environment: PMA_HOST: mysql PMA_PORT: 3306 networks: - laravel-dev depends_on: - mysql
networks: laravel-dev: driver: bridge
volumes: vendor_volume: driver: local mysql_data: driver: local
📄 4. docker-compose.prod.yml (Production with Local Storage) yamlversion: '3.8'
services: app: build: context: . dockerfile: Dockerfile container_name: laravel-app-prod restart: unless-stopped working_dir: /var/www env_file: - .env # Reads from host .env file volumes: - ./storage:/var/www/storage # For user uploads/logs networks: - laravel-prod
nginx: image: nginx:alpine container_name: laravel-nginx-prod restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./public:/var/www/public:ro - ./storage/app/public:/var/www/storage/app/public:ro # For storage symlink - ./docker/nginx/prod.conf:/etc/nginx/conf.d/default.conf - /etc/letsencrypt:/etc/letsencrypt:ro # SSL certificates networks: - laravel-prod depends_on: - app
networks: laravel-prod: driver: bridge
📄 5. docker-compose.prod-s3.yml (Production with S3) yamlversion: '3.8'
services: app: build: context: . dockerfile: Dockerfile container_name: laravel-app-prod restart: unless-stopped working_dir: /var/www env_file: - .env # Includes AWS S3 credentials volumes: - ./storage/logs:/var/www/storage/logs # Optional: keep logs on server networks: - laravel-prod
nginx: image: nginx:alpine container_name: laravel-nginx-prod restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./public:/var/www/public:ro # Only public assets (CSS/JS) - ./docker/nginx/prod.conf:/etc/nginx/conf.d/default.conf - /etc/letsencrypt:/etc/letsencrypt:ro # SSL certificates networks: - laravel-prod depends_on: - app
networks: laravel-prod: driver: bridge
📄 6. docker/nginx/default.conf (Dev/QA) nginxserver { listen 80; server_name localhost; root /var/www/public; index index.php index.html;
# File upload size
client_max_body_size 100M;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_read_timeout 300;
fastcgi_buffering off;
}
location ~ /\.ht {
deny all;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}
📄 7. docker/nginx/prod.conf (Production) nginx# HTTP - Redirect to HTTPS server { listen 80; server_name yourdomain.com www.yourdomain.com; return 301 https://$host$request_uri; }
server { listen 443 ssl http2; server_name yourdomain.com www.yourdomain.com; root /var/www/public; index index.php index.html;
# SSL Configuration (Let's Encrypt)
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# File upload size
client_max_body_size 100M;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param HTTPS on;
fastcgi_read_timeout 300;
fastcgi_buffering off;
}
location ~ /\.ht {
deny all;
}
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
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 "no-referrer-when-downgrade" always;
# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
📄 8. .env.example bashAPP_NAME=Laravel APP_ENV=local APP_KEY= APP_DEBUG=true APP_URL=http://localhost:8080
LOG_CHANNEL=stack LOG_LEVEL=debug
DB_CONNECTION=mysql DB_HOST=mysql DB_PORT=3306 DB_DATABASE=laravel DB_USERNAME=laravel DB_PASSWORD=secret
FILESYSTEM_DISK=local
CACHE_DRIVER=file QUEUE_CONNECTION=sync SESSION_DRIVER=file SESSION_LIFETIME=120
MAIL_MAILER=smtp MAIL_HOST=mailhog MAIL_PORT=1025 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS="hello@example.com" MAIL_FROM_NAME="${APP_NAME}"
📄 9. composer.json (Add S3 Support) json{ "require": { "php": "^8.2", "laravel/framework": "^11.0", "league/flysystem-aws-s3-v3": "^3.0" } } Install S3 package: bashcomposer require league/flysystem-aws-s3-v3
📄 10. docker/scripts/deploy-qa.sh bash#!/bin/bash set -e
echo "🚀 Deploying to QA Environment..."
cd /path/to/your/project
echo "📥 Pulling latest code..." git pull origin develop # or qa branch
echo "🐳 Building Docker images..." docker-compose -f docker-compose.yml down docker-compose -f docker-compose.yml build --no-cache docker-compose -f docker-compose.yml up -d
echo "⏳ Waiting for containers..." sleep 10
echo "📦 Installing dependencies..." docker-compose -f docker-compose.yml exec -T app composer install --optimize-autoloader
echo "🗄️ Running migrations..." docker-compose -f docker-compose.yml exec -T app php artisan migrate --force
echo "🧹 Clearing caches..." docker-compose -f docker-compose.yml exec -T app php artisan config:clear docker-compose -f docker-compose.yml exec -T app php artisan cache:clear docker-compose -f docker-compose.yml exec -T app php artisan view:clear
echo "📦 Caching configurations..." docker-compose -f docker-compose.yml exec -T app php artisan config:cache docker-compose -f docker-compose.yml exec -T app php artisan route:cache docker-compose -f docker-compose.yml exec -T app php artisan view:cache
echo "🔐 Setting permissions..." docker-compose -f docker-compose.yml exec -T app chmod -R 775 storage bootstrap/cache
echo "✅ QA Deployment completed successfully!" Make executable: bashchmod +x docker/scripts/deploy-qa.sh
📄 11. docker/scripts/deploy-prod.sh (Local Storage) bash#!/bin/bash set -e
echo "🚀 Deploying to Production (Local Storage)..."
cd /path/to/your/project
echo "📥 Pulling latest code..." git pull origin main
echo "🐳 Building Docker images..." docker-compose -f docker-compose.prod.yml build --pull --no-cache docker-compose -f docker-compose.prod.yml up -d
echo "⏳ Waiting for containers..." sleep 10
echo "🗄️ Running migrations..." docker-compose -f docker-compose.prod.yml exec -T app php artisan migrate --force
echo "🧹 Clearing caches..." docker-compose -f docker-compose.prod.yml exec -T app php artisan config:clear docker-compose -f docker-compose.prod.yml exec -T app php artisan cache:clear
echo "📦 Caching configurations..." docker-compose -f docker-compose.prod.yml exec -T app php artisan config:cache docker-compose -f docker-compose.prod.yml exec -T app php artisan route:cache docker-compose -f docker-compose.prod.yml exec -T app php artisan view:cache
echo "⚡ Optimizing..." docker-compose -f docker-compose.prod.yml exec -T app composer dump-autoload --optimize
echo "✅ Production Deployment completed successfully!" Make executable: bashchmod +x docker/scripts/deploy-prod.sh
📄 12. docker/scripts/deploy-prod-s3.sh (With S3) bash#!/bin/bash set -e
echo "🚀 Deploying to Production (with S3)..."
cd /path/to/your/project
echo "📥 Pulling latest code..." git pull origin main
echo "🐳 Building Docker images..." docker-compose -f docker-compose.prod-s3.yml build --pull --no-cache docker-compose -f docker-compose.prod-s3.yml up -d
echo "⏳ Waiting for containers..." sleep 10
echo "🗄️ Running migrations..." docker-compose -f docker-compose.prod-s3.yml exec -T app php artisan migrate --force
echo "🧹 Clearing caches..." docker-compose -f docker-compose.prod-s3.yml exec -T app php artisan config:clear docker-compose -f docker-compose.prod-s3.yml exec -T app php artisan cache:clear
echo "📦 Caching configurations..." docker-compose -f docker-compose.prod-s3.yml exec -T app php artisan config:cache docker-compose -f docker-compose.prod-s3.yml exec -T app php artisan route:cache docker-compose -f docker-compose.prod-s3.yml exec -T app php artisan view:cache
echo "⚡ Optimizing..." docker-compose -f docker-compose.prod-s3.yml exec -T app composer dump-autoload --optimize
echo "✅ Production Deployment (S3) completed successfully!" Make executable: bashchmod +x docker/scripts/deploy-prod-s3.sh
📄 13. .github/workflows/deploy-qa.yml yamlname: Deploy to QA
on: push: branches: [develop]
jobs: deploy: runs-on: ubuntu-latest
steps:
- name: Deploy to QA Server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.QA_EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd /path/to/your/project
git pull origin develop
docker-compose -f docker-compose.yml build --no-cache
docker-compose -f docker-compose.yml up -d
sleep 10
docker-compose -f docker-compose.yml exec -T app composer install --optimize-autoloader
docker-compose -f docker-compose.yml exec -T app php artisan migrate --force
docker-compose -f docker-compose.yml exec -T app php artisan config:cache
docker-compose -f docker-compose.yml exec -T app php artisan route:cache
docker-compose -f docker-compose.yml exec -T app php artisan view:cache
echo "QA Deployment completed!"
📄 14. .github/workflows/deploy-prod.yml (Local Storage) yamlname: Deploy to Production
on: push: branches: [main]
jobs: deploy: runs-on: ubuntu-latest
steps:
- name: Deploy to Production Server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PROD_EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd /path/to/your/project
git pull origin main
docker-compose -f docker-compose.prod.yml build --pull --no-cache
docker-compose -f docker-compose.prod.yml up -d
sleep 10
docker-compose -f docker-compose.prod.yml exec -T app php artisan migrate --force
docker-compose -f docker-compose.prod.yml exec -T app php artisan config:cache
docker-compose -f docker-compose.prod.yml exec -T app php artisan route:cache
docker-compose -f docker-compose.prod.yml exec -T app php artisan view:cache
docker-compose -f docker-compose.prod.yml exec -T app composer dump-autoload --optimize
echo "Production Deployment completed!"
📄 15. .github/workflows/deploy-prod-s3.yml (With S3) yamlname: Deploy to Production (S3)
on: push: branches: [main]
jobs: deploy: runs-on: ubuntu-latest
steps:
- name: Deploy to Production Server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PROD_EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd /path/to/your/project
git pull origin main
docker-compose -f docker-compose.prod-s3.yml build --pull --no-cache
docker-compose -f docker-compose.prod-s3.yml up -d
sleep 10
docker-compose -f docker-compose.prod-s3.yml exec -T app php artisan migrate --force
docker-compose -f docker-compose.prod-s3.yml exec -T app php artisan config:cache
docker-compose -f docker-compose.prod-s3.yml exec -T app php artisan route:cache
docker-compose -f docker-compose.prod-s3.yml exec -T app php artisan view:cache
docker-compose -f docker-compose.prod-s3.yml exec -T app composer dump-autoload --optimize
echo "Production Deployment (S3) completed!"
🚀 First-Time Setup Instructions Dev/QA Environment: bash# 1. Clone repository git clone your-repo.git cd your-project
cp .env.example .env nano .env # Edit DB credentials
docker-compose up -d --build
docker-compose exec app composer install
docker-compose exec app php artisan key:generate
docker-compose exec app php artisan migrate
docker-compose exec app php artisan storage:link
docker-compose exec app chmod -R 775 storage bootstrap/cache
phpMyAdmin: http://localhost:8081
Production Environment (Local Storage): bash# 1. SSH to server ssh ubuntu@your-server-ip
git clone your-repo.git /var/www/your-project cd /var/www/your-project
cp .env.example .env nano .env # Edit with production values:
sudo apt install certbot python3-certbot-nginx sudo certbot certonly --nginx -d yourdomain.com -d www.yourdomain.com
docker-compose -f docker-compose.prod.yml up -d --build
docker-compose -f docker-compose.prod.yml exec app php artisan key:generate
docker-compose -f docker-compose.prod.yml exec app php artisan migrate --force
docker-compose -f docker-compose.prod.yml exec app php artisan storage:link
docker-compose -f docker-compose.prod.yml exec app php artisan config:cache docker-compose -f docker-compose.prod.yml exec app php artisan route:cache docker-compose -f docker-compose.prod.yml exec app php artisan view:cache
sudo chown -R ubuntu:ubuntu . chmod -R 775 storage bootstrap/cache
Production Environment (with S3): bash# 1. SSH to server ssh ubuntu@your-server-ip
git clone your-repo.git /var/www/your-project cd /var/www/your-project
cp .env.example .env nano .env # Edit with:
- AWS_URL=https://your-bucket.s3.amazonaws.com
sudo apt install certbot python3-certbot-nginx sudo certbot certonly --nginx -d yourdomain.com -d www.yourdomain.com
docker-compose -f docker-compose.prod-s3.yml up -d --build
docker-compose -f docker-compose.prod-s3.yml exec app php artisan key:generate
docker-compose -f docker-compose.prod-s3.yml exec app php artisan migrate --force
docker-compose -f docker-compose.prod-s3.yml exec app php artisan config:cache docker-compose -f docker-compose.prod-s3.yml exec app php artisan route:cache docker-compose -f docker-compose.prod-s3.yml exec app php artisan view:cache
📊 Quick Reference Table FeatureDev/QAProduction (Local Storage)Production (S3)Docker Compose Filedocker-compose.ymldocker-compose.prod.ymldocker-compose.prod-s3.ymlDockerfileDockerfileDockerfileDockerfileDatabaseMySQL in containerAWS RDSAWS RDSFile StorageLocal /storageLocal /storageAWS S3phpMyAdmin✅ Included❌ Not needed❌ Not neededStorage Mount./:/var/www./storage:/var/www/storage./storage/logs onlyVendor MountNamed volume❌ Baked in image❌ Baked in imageSSL❌ Not needed✅ Let's Encrypt✅ Let's EncryptNginx Configdefault.confprod.confprod.confstorage:link✅ Required✅ Required❌ Not neededPort808080, 44380, 443GitHub Actiondeploy-qa.ymldeploy-prod.ymldeploy-prod-s3.ymlDeploy Scriptdeploy-qa.shdeploy-prod.shdeploy-prod-s3.sh
🔑 GitHub Secrets to Set Go to your repository: Settings → Secrets and variables → Actions For QA:
QA_EC2_HOST = QA server IP EC2_USER = ubuntu EC2_SSH_KEY = Your SSH private key
For Production:
PROD_EC2_HOST = Production server IP EC2_USER = ubuntu EC2_SSH_KEY = Your SSH private key
📝 Common Commands Reference Dev/QA: bash# Start docker-compose up -d
docker-compose down
docker-compose up -d --build
docker-compose logs -f app
docker-compose exec app php artisan migrate docker-compose exec app php artisan tinker
docker-compose exec app bash Production (Local Storage): bash# Deploy ./docker/scripts/deploy-prod.sh
docker-compose -f docker-compose.prod.yml up -d --build
docker-compose -f docker-compose.prod.yml logs -f app
docker-compose -f docker-compose.prod.yml exec app php artisan migrate --force Production (S3): bash# Deploy ./docker/scripts/deploy-prod-s3.sh
docker-compose -f docker-compose.prod-s3.yml up -d --build
docker-compose -f docker-compose.prod-s3.yml logs -f app
docker-compose -f docker-compose.prod-s3.yml exec app php artisan migrate --force
✅ Final Checklist Dev/QA Setup:
Dockerfile created docker-compose.yml created .dockerignore created nginx/default.conf created .env configured with MySQL credentials Vendor volume mounted phpMyAdmin accessible
Production Setup (Local Storage):
docker-compose.prod.yml created nginx/prod.conf created with your domain SSL certificates obtained with certbot .env created on server with RDS credentials storage/ directory mounted storage:link created GitHub Actions workflow configured
Production Setup (S3):
docker-compose.prod-s3.yml created S3 bucket created with proper permissions IAM user created with S3 access .env configured with AWS credentials FILESYSTEM_DISK=s3 in .env league/flysystem-aws-s3-v3 installed NO storage mount (only logs optional) GitHub Actions workflow configured