Skip to main content
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:
  1. Docker and Docker Compose
  2. Environment configuration
  3. Database setup and migrations
  4. Asset compilation
  5. Application setup

Docker Deployment

The application is defined in docker-compose.yml with three core services:

Service Architecture

1

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
2

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
3

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

php artisan key:generate
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:
composer setup
This command runs:
1

Install Composer Dependencies

composer install
Installs all PHP packages defined in composer.json:8-21:
  • Laravel Framework 12.0
  • Laravel Tinker
  • Development tools (Pint, Sail, PHPUnit, etc.)
2

Create Environment File

php -r "file_exists('.env') || copy('.env.example', '.env');"
Creates .env if it doesn’t exist
3

Generate Application Key

php artisan key:generate
Generates APP_KEY in .env
4

Run Database Migrations

php artisan migrate --force
Creates all database tables (force flag skips confirmation in production)
5

Install Node Dependencies

npm install
Installs frontend dependencies from package.json:9-16
6

Build Production Assets

npm run build
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:
1

Clone Repository

git clone https://github.com/your-org/macuin-laravel.git
cd macuin-laravel
2

Configure Environment

cp .env.example .env
# Edit .env with production settings
vi .env
3

Build Docker Images

docker-compose build --no-cache
4

Start Services

docker-compose up -d postgres app nginx
Note: Exclude the node service in production
5

Install Dependencies

docker-compose exec app composer install --no-dev --optimize-autoloader
Production flags:
  • --no-dev: Excludes development dependencies
  • --optimize-autoloader: Optimizes class loading
6

Generate Application Key

docker-compose exec app php artisan key:generate
7

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
8

Run Migrations

docker-compose exec app php artisan migrate --force
9

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
10

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:
  1. Backup the database
    docker-compose exec postgres pg_dump -U laravel laravel > backup.sql
    
  2. Test migrations in staging environment
  3. Review migration files for destructive operations
  4. Plan rollback strategy
    php artisan migrate:rollback --force
    

Zero-Downtime Migrations

For critical applications:
  1. Use database replicas
  2. Make migrations backward-compatible
  3. 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:
npm run build
This executes vite build with optimizations:
1

JavaScript Minification

  • Dead code elimination
  • Variable name mangling
  • Bundle splitting
  • Tree-shaking
2

CSS Optimization

  • Tailwind CSS purging (removes unused classes)
  • CSS minification
  • Source map generation (optional)
  • Asset hashing for cache busting
3

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:
composer test
This runs:
  1. php artisan config:clear --ansi - Clear cached config
  2. php artisan test - Run PHPUnit test suite

Testing in Docker

docker-compose exec app composer test

Pre-Deployment Testing

Before deploying:
1

Run Full Test Suite

composer test
2

Code Quality Checks

vendor/bin/pint --test
Runs Laravel Pint linter without making changes
3

Build Assets Locally

npm run build
Verify no build errors
4

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

1

Pre-Deployment

  • Run tests: composer test
  • Review code changes and migrations
  • Backup production database
  • Build assets locally: npm run build
  • Test in staging environment
  • Update .env with production values
2

Deployment

  • Pull latest code: git pull origin main
  • Install dependencies: composer install --no-dev --optimize-autoloader
  • Build assets: npm run build
  • Run migrations: php artisan migrate --force
  • Clear caches: php artisan config:clear
  • Optimize application: php artisan optimize
3

Post-Deployment

  • Verify application is accessible
  • Check /health endpoint
  • Monitor error logs
  • Test critical user flows
  • Verify asset loading (check browser console)
  • Monitor database connections
4

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

Build docs developers (and LLMs) love