Skip to main content

Services

File: docker-compose.yaml
ServiceImagePortPurpose
appphp:7.2-fpm-alpine3.119000, 2525PHP-FPM application server
webservernginx:alpine80, 443Nginx reverse proxy
mysql-dbmysql:5.73306Primary database
mysql-db-testmysql:5.73310Test database
redisredis6379Cache, sessions, queue
blackboxtutorbloc.blackbox3000Distance calculation microservice
All services on the tb.network Docker network.

Database defaults

Primary:
MYSQL_DATABASE=tutorbloc_db
MYSQL_USER=admin
MYSQL_PASSWORD=secret
MYSQL_ROOT_PASSWORD=secret
Test:
MYSQL_DATABASE=tutorbloc_db_test
MYSQL_USER=tester
MYSQL_PASSWORD=tester_secret
MYSQL_ROOT_PASSWORD=secret

Dockerfile

File: Dockerfile Multi-stage build:

Stage 1: Nginx

FROM nginx:alpine
# Copies nginx config and public directory

Stage 2: App (PHP-FPM)

FROM php:7.2-fpm-alpine3.11
WORKDIR /src
System dependencies installed:
  • libxml2, bash, sudo
  • freetype, libjpeg, libpng (image processing)
  • libzip, curl, imap, xslt
  • PostgreSQL libs, libgcrypt, oniguruma
PHP extensions:
  • gd (with freetype/jpeg/png support)
  • bcmath, ctype, json
  • pdo, pdo_mysql, mysqli
  • tokenizer, xml
Additional tools:
  • Dockerize v0.6.1 (wait-for-service utility)
  • Composer (dependency installation)

Container startup

The entrypoint runs:
php artisan migrate --force    # Run pending migrations
crond                          # Start cron daemon (scheduler)
php-fpm                        # Start PHP-FPM
Migrations run on every container start. If a migration fails, the container fails to start. Be careful with migration changes in production.

Permissions

The Dockerfile sets www-data:www-data ownership on:
  • bootstrap/
  • storage/
  • public/

Nginx configuration

File: app-nginx.conf
server {
    listen 80 default_server;
    root /src/public;
    client_max_body_size 100M;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
        gzip_static on;
    }

    location ~ \.php$ {
        fastcgi_pass app:9000;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}
Key settings:
  • Max upload size: 100MB
  • PHP handling: FastCGI pass to app:9000
  • Gzip: Static gzip enabled
  • All routes fall through to index.php (Laravel routing)

Volumes

Docker Compose mounts:
  • Source code → /src
  • vendor/ → named volume (persists between rebuilds)
  • storage/ → named volume
  • bootstrap/ → named volume
  • public/ → shared between app and nginx
Database volumes:
  • ./data → MySQL primary data
  • ./data_test → MySQL test data
data/ and data_test/ directories are gitignored. They persist database state between Docker restarts.

Common commands

# Start all services
docker-compose up -d

# View logs
docker-compose logs -f app

# Enter app container
docker-compose exec app sh

# Run artisan commands
docker-compose exec app php artisan migrate
docker-compose exec app php artisan tinker
docker-compose exec app php artisan queue:work

# Run tests
docker-compose exec app vendor/bin/phpunit

# Rebuild containers
docker-compose up -d --build

# Stop all services
docker-compose down