This guide covers deploying Macuin Laravel using Docker, including environment configuration, database migrations, and asset building for production.
Deployment Overview
Macuin Laravel is containerized with Docker, making deployment consistent across environments. The production deployment requires:
- Docker and Docker Compose
- Environment configuration
- Database setup and migrations
- Asset compilation
- Application setup
Docker Deployment
The application is defined in docker-compose.yml with three core services:
Service Architecture
PostgreSQL Database
Defined in docker-compose.yml:2-19:postgres:
image: postgres:16-alpine
container_name: laravel_postgres
environment:
POSTGRES_DB: laravel
POSTGRES_USER: laravel
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U laravel -d laravel"]
interval: 5s
timeout: 3s
retries: 20
Features:
- PostgreSQL 16 Alpine (lightweight)
- Health checks for reliable startup
- Persistent data volume
- Ready for production with custom credentials
PHP Application Container
Defined in docker-compose.yml:21-43:app:
build:
context: .
dockerfile: docker/php/Dockerfile
args:
UID: "1000"
GID: "1000"
container_name: laravel_app
working_dir: /var/www/html
volumes:
- ./:/var/www/html
environment:
DB_CONNECTION: pgsql
DB_HOST: postgres
DB_PORT: 5432
DB_DATABASE: laravel
DB_USERNAME: laravel
DB_PASSWORD: secret
depends_on:
postgres:
condition: service_healthy
Features:
- Custom PHP image with required extensions
- Waits for database health check
- User/group ID mapping for file permissions
- Environment variables for database connection
Nginx Web Server
Defined in docker-compose.yml:45-56:nginx:
image: nginx:1.27-alpine
container_name: laravel_nginx
ports:
- "8000:80"
volumes:
- ./:/var/www/html:ro
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- app
Features:
- Nginx 1.27 Alpine
- Port 8000 exposed (change for production)
- Read-only volume mounts for security
- Custom configuration for Laravel
The Node service (docker-compose.yml:58-68) is only needed for development. Exclude it in production or use it only for the build step.
Environment Configuration
Production Environment File
Create a .env file based on .env.example:1-66:
APP_NAME="Macuin Laravel"
APP_ENV=production
APP_KEY=base64:YOUR_GENERATED_KEY
APP_DEBUG=false
APP_URL=https://yourdomain.com
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
Critical Security Settings:
- Set
APP_DEBUG=false in production
- Use strong, unique passwords for database
- Generate a new
APP_KEY for each environment
- Never commit
.env files to version control
Generating Application Key
Or within Docker:
docker-compose exec app php artisan key:generate
Deployment Process
Automated Setup Script
The project includes a comprehensive setup script in composer.json:35-42:
This command runs:
Install Composer Dependencies
Installs all PHP packages defined in composer.json:8-21:
- Laravel Framework 12.0
- Laravel Tinker
- Development tools (Pint, Sail, PHPUnit, etc.)
Create Environment File
php -r "file_exists('.env') || copy('.env.example', '.env');"
Creates .env if it doesn’t existGenerate Application Key
Generates APP_KEY in .env Run Database Migrations
php artisan migrate --force
Creates all database tables (force flag skips confirmation in production)Install Node Dependencies
Installs frontend dependencies from package.json:9-16 Build Production Assets
Compiles and optimizes frontend assets
The composer setup script is idempotent - safe to run multiple times.
Manual Deployment Steps
For more control, run steps individually:
Clone Repository
git clone https://github.com/your-org/macuin-laravel.git
cd macuin-laravel
Configure Environment
cp .env.example .env
# Edit .env with production settings
vi .env
Build Docker Images
docker-compose build --no-cache
Start Services
docker-compose up -d postgres app nginx
Note: Exclude the node service in productionInstall Dependencies
docker-compose exec app composer install --no-dev --optimize-autoloader
Production flags:
--no-dev: Excludes development dependencies
--optimize-autoloader: Optimizes class loading
Generate Application Key
docker-compose exec app php artisan key:generate
Build Frontend Assets
Build locally or in a separate container:npm install
npm run build
Or with Docker:docker-compose run --rm node npm install
docker-compose run --rm node npm run build
Run Migrations
docker-compose exec app php artisan migrate --force
Optimize Application
docker-compose exec app php artisan config:cache
docker-compose exec app php artisan route:cache
docker-compose exec app php artisan view:cache
Set Permissions
docker-compose exec app chown -R www-data:www-data storage bootstrap/cache
docker-compose exec app chmod -R 775 storage bootstrap/cache
Database Migrations
Running Migrations in Production
The --force flag bypasses the production warning:
php artisan migrate --force
Or with Docker:
docker-compose exec app php artisan migrate --force
Migration Best Practices
Before migrating in production:
-
Backup the database
docker-compose exec postgres pg_dump -U laravel laravel > backup.sql
-
Test migrations in staging environment
-
Review migration files for destructive operations
-
Plan rollback strategy
php artisan migrate:rollback --force
Zero-Downtime Migrations
For critical applications:
- Use database replicas
- Make migrations backward-compatible
- Deploy in stages:
- Deploy code compatible with old and new schema
- Run migrations
- Deploy code using new schema
Asset Building for Production
Production Build Command
From package.json:6:
This executes vite build with optimizations:
JavaScript Minification
- Dead code elimination
- Variable name mangling
- Bundle splitting
- Tree-shaking
CSS Optimization
- Tailwind CSS purging (removes unused classes)
- CSS minification
- Source map generation (optional)
- Asset hashing for cache busting
Output Generation
Builds to public/build/ directory:public/build/
├── assets/
│ ├── app-[hash].js
│ └── app-[hash].css
└── manifest.json
Build Configuration
Vite production settings (vite.config.js:1-27):
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
}),
tailwindcss(),
],
// Server config only used in dev
});
The refresh: true option is ignored in production builds. It only affects development HMR.
Verifying Build Output
Check build success:
ls -lah public/build/assets/
Expected output:
app-[hash].js # Main JavaScript bundle
app-[hash].css # Main stylesheet
Testing
Running Tests
The test script is defined in composer.json:47-50:
This runs:
php artisan config:clear --ansi - Clear cached config
php artisan test - Run PHPUnit test suite
Testing in Docker
docker-compose exec app composer test
Pre-Deployment Testing
Before deploying:
Code Quality Checks
Runs Laravel Pint linter without making changes Test Database Migrations
In staging environment:php artisan migrate --force
php artisan migrate:rollback --force
php artisan migrate --force
Production Optimization
Laravel Optimizations
Run after each deployment:
# Configuration caching
php artisan config:cache
# Route caching
php artisan route:cache
# View compilation
php artisan view:cache
# Event & listener caching (if applicable)
php artisan event:cache
Or as a Docker command:
docker-compose exec app php artisan optimize
Cached configuration means .env changes won’t take effect until you run php artisan config:clear.
Composer Optimizations
Use production flags:
composer install --no-dev --optimize-autoloader --classmap-authoritative
Flags explained:
--no-dev: Excludes development dependencies (smaller deployment)
--optimize-autoloader: Generates optimized class maps
--classmap-authoritative: Never scan filesystem for classes
PHP Configuration
Recommended php.ini settings for production:
memory_limit = 256M
max_execution_time = 60
upload_max_filesize = 10M
post_max_size = 10M
opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 8
opcache.max_accelerated_files = 10000
opcache.validate_timestamps = 0
Monitoring & Maintenance
Log Management
View logs:
# Application logs
docker-compose exec app tail -f storage/logs/laravel.log
# Nginx access logs
docker-compose logs -f nginx
# PostgreSQL logs
docker-compose logs -f postgres
Database Backups
Automate PostgreSQL backups:
#!/bin/bash
# backup.sh
BACKUP_DIR="/backups"
DATE=$(date +"%Y%m%d_%H%M%S")
FILENAME="macuin_backup_${DATE}.sql"
docker-compose exec -T postgres pg_dump -U laravel laravel > "${BACKUP_DIR}/${FILENAME}"
gzip "${BACKUP_DIR}/${FILENAME}"
# Keep only last 7 days
find "${BACKUP_DIR}" -name "macuin_backup_*.sql.gz" -mtime +7 -delete
Schedule with cron:
0 2 * * * /path/to/backup.sh
Health Checks
Create a health endpoint:
// routes/web.php
Route::get('/health', function () {
return response()->json([
'status' => 'healthy',
'database' => DB::connection()->getPdo() ? 'connected' : 'disconnected',
'cache' => Cache::has('test') ? 'working' : 'unavailable',
]);
});
Deployment Checklist
Rollback Plan
If issues arise:# Rollback code
git checkout previous-commit-hash
# Rollback migrations
php artisan migrate:rollback --force
# Restore database backup
docker-compose exec -T postgres psql -U laravel laravel < backup.sql
# Rebuild assets from previous version
npm run build
# Clear all caches
php artisan optimize:clear
CI/CD Integration
Example GitHub Actions workflow:
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- name: Install Composer dependencies
run: composer install --no-dev --optimize-autoloader
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '22'
- name: Build assets
run: |
npm install
npm run build
- name: Run tests
run: composer test
- name: Deploy to server
run: |
# Your deployment commands here
ssh user@server 'cd /var/www && git pull'
ssh user@server 'cd /var/www && php artisan migrate --force'
ssh user@server 'cd /var/www && php artisan optimize'
Best Practices
Security
- Use environment variables for secrets
- Enable HTTPS in production
- Keep dependencies updated
- Use
--no-dev for Composer in production
- Disable debug mode (
APP_DEBUG=false)
Performance
- Cache configuration and routes
- Use optimized autoloader
- Enable OPcache for PHP
- Serve static assets via CDN
- Use database connection pooling
Reliability
- Implement health checks
- Automate database backups
- Monitor application logs
- Test migrations in staging
- Have rollback procedures ready
Scalability
- Use queue workers for background jobs
- Implement caching (Redis/Memcached)
- Use load balancers for multiple instances
- Separate static assets to CDN
- Monitor resource usage
Troubleshooting
Common Issues
Issue: Permission denied on storage/ or bootstrap/cache/
Solution:
chmod -R 775 storage bootstrap/cache
chown -R www-data:www-data storage bootstrap/cache
Issue: Assets not loading (404 errors)
Solution:
# Rebuild assets
npm run build
# Verify manifest exists
ls -la public/build/manifest.json
# Check Nginx serves static files
curl http://localhost:8000/build/assets/app-*.js
Issue: Database connection failed
Solution:
# Check PostgreSQL is running
docker-compose ps postgres
# Verify connection from app container
docker-compose exec app php artisan tinker
>>> DB::connection()->getPdo();
# Check environment variables
docker-compose exec app env | grep DB_
Issue: Cached configuration causing issues
Solution:
php artisan optimize:clear
This clears:
- Configuration cache
- Route cache
- View cache
- Event cache
Next Steps
Frontend Development
Learn about asset compilation and Vite
Styling
Explore the CSS architecture and design system