Skip to content

Add comprehensive Docker support for local development and production#206

Merged
alimranahmed merged 12 commits intomainfrom
imran/feature/202-add-docker-support
Apr 14, 2026
Merged

Add comprehensive Docker support for local development and production#206
alimranahmed merged 12 commits intomainfrom
imran/feature/202-add-docker-support

Conversation

@alimranahmed
Copy link
Copy Markdown
Owner

Summary

Implements #202 - Add Docker support for local development and deployment

This PR adds a complete Docker setup that allows running the entire LaraBlog application with a single docker compose up -d command.

Changes

Docker Services

  • app: PHP 8.2 FPM with Laravel application
  • nginx: Alpine-based web server
  • mysql: MySQL 8.4 with health checks
  • redis: Redis 7 for caching and sessions
  • mailpit: Email testing tool (replaces Mailhog)

Key Features

Production Ready:
✅ Single command deployment: docker compose up -d
✅ Automatic database migrations on startup
✅ Cache optimization (config, routes, views)
✅ Queue workers with Supervisor auto-restart
✅ Task scheduler included
✅ Health checks for service dependencies
✅ Persistent volumes for data
✅ OPcache enabled for performance

Development Friendly:
✅ Hot-reload via volume mounting
✅ One-command setup: ./docker-setup.sh
✅ Makefile shortcuts (make up, make logs, etc.)
✅ Mailpit UI at localhost:8025
✅ Easy container and log access
✅ Local environment preconfigured

Lightweight & Secure:
✅ Alpine Linux base images
✅ Latest stable versions (PHP 8.2, MySQL 8.4, Redis 7)
✅ Multi-stage Docker builds
✅ Minimal attack surface
✅ Secure production defaults

Files Added

  • Dockerfile - Multi-stage build for dev/prod
  • docker-compose.yml - Service orchestration
  • docker-setup.sh - Automated setup script
  • Makefile - 20+ convenience commands
  • DOCKER.md - Comprehensive documentation
  • DOCKER-QUICKREF.md - Quick reference card
  • .env.docker - Environment template
  • .dockerignore - Build optimization
  • docker/nginx/ - Nginx configuration
  • docker/php/ - PHP settings & OPcache
  • docker/mysql/ - MySQL optimization
  • docker/supervisor/ - Queue workers & scheduler
  • Updated README.md with Docker installation option

Quick Start

For Local Development

./docker-setup.sh
# Access: http://localhost
# Mailpit: http://localhost:8025

For Production

cp .env.docker .env
# Edit .env with production values
docker compose up -d

Testing

Tested with Docker Compose v2. All services start correctly with proper health checks and dependencies.

Documentation

Migration Notes

  • No breaking changes to existing setup
  • Docker is completely optional
  • Traditional installation still works
  • Can be deployed alongside existing infrastructure

Closes #202

alimranahmed and others added 2 commits April 8, 2026 00:04
Implements issue #202 - Add Docker support for local development and deployment

This commit adds a complete Docker setup that allows running the entire
LaraBlog application with a single 'docker compose up -d' command.

Features:
- Multi-stage Dockerfile with separate development and production targets
- PHP 8.2 FPM with all required extensions (pdo, mysql, redis, gd, zip, bcmath)
- Nginx web server (Alpine-based for lightweight deployment)
- MySQL 8.4 with health checks and optimized configuration
- Redis 7 for caching and sessions
- Mailpit for email testing (replaces Mailhog)
- Supervisor for queue workers and task scheduling in production
- Automatic migrations and cache optimization in production

Docker Services:
- app: Laravel application (PHP-FPM)
- nginx: Web server
- mysql: Database server
- redis: Cache/session store
- mailpit: Email testing tool

Configuration Files:
- Dockerfile: Multi-stage build for dev/prod environments
- docker-compose.yml: Service orchestration with health checks
- docker/nginx/default.conf: Nginx configuration for Laravel
- docker/php/php.ini: PHP settings (memory, upload limits)
- docker/php/opcache.ini: OPcache optimization for production
- docker/mysql/my.cnf: MySQL optimization settings
- docker/supervisor/supervisord.conf: Queue workers and scheduler
- docker/entrypoint.sh: Initialization script for production
- .dockerignore: Optimize build context
- .env.docker: Environment template for Docker

Developer Experience:
- docker-setup.sh: One-command automated setup script
- Makefile: Common operations (make up, make down, make logs, etc.)
- DOCKER.md: Comprehensive documentation
- Updated README.md with Docker installation option

Production Ready:
- Single command deployment: docker compose up -d
- Automatic database migrations on startup
- Cache optimization (config, routes, views)
- Queue workers with auto-restart
- Persistent volumes for MySQL and Redis data
- Health checks for service dependencies
- Secure defaults with opcache enabled

Development Friendly:
- Hot-reload support via volume mounting
- Easy access to logs and containers
- Mailpit UI at localhost:8025
- Local environment preconfigured
- All services accessible on standard ports

The setup uses latest stable versions of all services with
lightweight Alpine Linux base images where possible.

Closes #202

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a concise quick reference card for common Docker operations,
making it easier for developers to find frequently used commands.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 7, 2026 22:06
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a full Docker + Docker Compose setup to run LaraBlog locally (and intended for production), along with helper scripts and documentation to streamline environment setup.

Changes:

  • Introduces multi-service docker-compose.yml (app/nginx/mysql/redis/mailpit) plus Dockerfile build stages.
  • Adds operational glue: entrypoint script, Supervisor config (queue worker + scheduler), PHP/Nginx/MySQL tuning.
  • Adds developer UX: docker-setup.sh, Makefile shortcuts, and Docker documentation/quick reference; README updated with Docker install path.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
README.md Adds Docker installation instructions alongside traditional setup.
Makefile Adds convenience targets for Compose workflows (up/logs/artisan/etc.).
Dockerfile Adds PHP-FPM Alpine image with Composer/NPM build steps + dev/prod stages.
docker-compose.yml Orchestrates app/nginx/mysql/redis/mailpit services, volumes, and healthchecks.
docker-setup.sh Automates local bootstrap: .env creation, compose up, key generation, migrate/seed.
docker/entrypoint.sh Production entrypoint: waits for DB, runs migrations + caches, then starts Supervisor.
docker/supervisor/supervisord.conf Runs php-fpm, queue worker, and scheduler under Supervisor.
docker/nginx/default.conf Nginx vhost config for Laravel + PHP-FPM upstream.
docker/php/php.ini PHP runtime settings for uploads/memory/timeouts/timezone.
docker/php/opcache.ini Enables OPcache with production-oriented settings.
docker/mysql/my.cnf MySQL server/client settings and charset/collation defaults.
.env.docker Docker-oriented env template (DB/Redis/Mailpit defaults).
.dockerignore Excludes dev/artifacts from build context to speed up builds.
DOCKER.md Full Docker usage guide (local + production workflow).
DOCKER-QUICKREF.md Command quick reference card for common Docker/Make targets.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docker-compose.yml Outdated
build:
context: .
dockerfile: Dockerfile
target: ${APP_ENV:-production}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build.target is derived from APP_ENV, but the Dockerfile stages are named development and production. With .env set to APP_ENV=local (as docker-setup.sh does), docker compose build will try to build a non-existent local stage and fail. Use a separate variable (e.g., DOCKER_TARGET) or align APP_ENV values/stage names so development builds select the development stage.

Suggested change
target: ${APP_ENV:-production}
target: ${DOCKER_TARGET:-production}

Copilot uses AI. Check for mistakes.
Comment thread docker-compose.yml
Comment on lines +9 to +12
working_dir: /var/www/html
volumes:
- ./:/var/www/html
- ./docker/php/php.ini:/usr/local/etc/php/conf.d/custom.ini
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The app service bind-mounts the entire project (./:/var/www/html). In production this overrides everything baked into the image (vendor/, built Vite assets, cached config), which can break the “production-ready” flow and negates the multi-stage build. Consider moving bind mounts to a docker-compose.override.yml for local dev, and in production mount only persistent paths (e.g., storage/) via named volumes.

Copilot uses AI. Check for mistakes.
Comment thread docker-compose.yml
- larablog

mailpit:
image: axllent/mailpit:latest
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using axllent/mailpit:latest makes environments non-reproducible (updates to latest can break builds unexpectedly). For production readiness, pin to a specific Mailpit version tag and bump intentionally.

Suggested change
image: axllent/mailpit:latest
image: axllent/mailpit:v1.20.5

Copilot uses AI. Check for mistakes.
Comment thread docker/php/opcache.ini
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2
opcache.save_comments=1
opcache.validate_timestamps=0
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

opcache.validate_timestamps=0 disables PHP file change detection, which will make hot-reload / bind-mounted code changes not take effect without restarting PHP-FPM. Since this config is copied into the shared base stage, it impacts development too. Consider using a separate OPcache config for dev (validate_timestamps=1) vs prod, or conditionally enabling OPcache based on environment.

Suggested change
opcache.validate_timestamps=0
opcache.validate_timestamps=1

Copilot uses AI. Check for mistakes.
Comment thread Dockerfile
Comment on lines +7 to +21
# Install system dependencies and PHP extensions
RUN apk add --no-cache \
git \
curl \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
libzip-dev \
zip \
unzip \
mysql-client \
nodejs \
npm \
supervisor \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The production stage is FROM base, and base installs build-time tools like git/nodejs/npm. This contradicts the stated goal of a lightweight/minimal-attack-surface production image and increases image size and CVE surface. Consider a true multi-stage approach: build assets/deps in a builder stage, then copy only vendor/ + built assets into a slim runtime stage without node/npm/git.

Copilot uses AI. Check for mistakes.
Comment thread docker/entrypoint.sh Outdated
Comment on lines +6 to +8
until php artisan db:show > /dev/null 2>&1; do
echo "MySQL is unavailable - sleeping"
sleep 2
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MySQL wait loop has no timeout and will block container startup indefinitely if credentials are wrong or the DB never becomes reachable. For more predictable behavior (especially in production), add a max retry/timeout and exit non-zero so orchestrators can surface a clear failure and restart/backoff appropriately.

Suggested change
until php artisan db:show > /dev/null 2>&1; do
echo "MySQL is unavailable - sleeping"
sleep 2
MYSQL_MAX_RETRIES="${MYSQL_MAX_RETRIES:-30}"
MYSQL_RETRY_INTERVAL="${MYSQL_RETRY_INTERVAL:-2}"
attempt=1
until php artisan db:show > /dev/null 2>&1; do
if [ "$attempt" -ge "$MYSQL_MAX_RETRIES" ]; then
echo "MySQL is unavailable after $attempt attempts - exiting"
exit 1
fi
echo "MySQL is unavailable - sleeping (${attempt}/${MYSQL_MAX_RETRIES})"
attempt=$((attempt + 1))
sleep "$MYSQL_RETRY_INTERVAL"

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +4
[supervisord]
nodaemon=true
user=root
logfile=/var/log/supervisor/supervisord.log
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

supervisord is configured to run as root and the queue worker / scheduler programs don’t override user=..., so those long-running artisan processes will run as root. This is both a security concern and can create root-owned files in bind-mounted volumes (leading to permission issues on the host). Consider running supervisor and/or the artisan programs as www-data (or a dedicated non-root user) and only keeping root where strictly required.

Copilot uses AI. Check for mistakes.
Comment thread Dockerfile Outdated
Comment on lines +43 to +51
# Copy application files
COPY . .

# Install PHP dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist

# Install Node dependencies and build assets
RUN npm ci && npm run build && rm -rf node_modules

Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

COPY . . happens before dependency installation, so any change in the repo invalidates the Docker cache and forces a full composer install + npm ci rebuild. To speed up rebuilds, copy composer.json/composer.lock and package*.json separately, install deps, then copy the rest of the source.

Suggested change
# Copy application files
COPY . .
# Install PHP dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist
# Install Node dependencies and build assets
RUN npm ci && npm run build && rm -rf node_modules
# Copy dependency manifests first to maximize Docker cache reuse
COPY composer.json composer.lock ./
COPY package*.json ./
# Install PHP dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist
# Install Node dependencies
RUN npm ci
# Copy application files
COPY . .
# Build assets after the source is available, then clean up Node modules
RUN npm run build && rm -rf node_modules

Copilot uses AI. Check for mistakes.
Comment thread docker-compose.yml Outdated
Comment on lines +59 to +60
ports:
- "${DB_PORT:-3306}:3306"
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MySQL service publishes port 3306 to the host by default. For production deployments this is usually unnecessary and weakens the “secure defaults” story (it exposes the DB to anything that can reach the host). Consider removing the ports: mapping (Compose network access is enough) or gating it behind a dev-only override file/profile.

Suggested change
ports:
- "${DB_PORT:-3306}:3306"

Copilot uses AI. Check for mistakes.
Comment thread docker-compose.yml
Comment on lines +76 to +77
ports:
- "${REDIS_PORT:-6379}:6379"
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Redis service publishes port 6379 to the host by default. That’s convenient for local dev, but for production it increases exposure without authentication/TLS. Consider removing the host port mapping or making it dev-only (e.g., via override file/profile), relying on internal Compose networking instead.

Suggested change
ports:
- "${REDIS_PORT:-6379}:6379"

Copilot uses AI. Check for mistakes.
alimranahmed and others added 9 commits April 8, 2026 00:15
Changes:
- Use mysql:8 instead of mysql:8.4-oracle for simplicity
- Update to redis:8-alpine (latest version)
- Create ./run script for easy command execution in container
- Simplify Makefile to essential commands only
- Remove DOCKER-QUICKREF.md (redundant with DOCKER.md)
- Update all documentation to use ./run script
- Update README with cleaner instructions

The ./run script provides a cleaner interface:
  ./run php artisan migrate
  ./run composer install
  ./run npm run dev
  ./run sh

Minimal Makefile now includes only:
  make up/down/restart/logs/shell/clean/fresh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update default ports to avoid conflicts with other applications:
- Web app: 80 -> 1013 (APP_PORT)
- Mailpit UI: 8025 -> 1028 (MAILPIT_UI_PORT)

This prevents port conflicts when running multiple Laravel apps
with Mailpit on the same machine.

Updated in:
- docker-compose.yml
- .env.docker
- DOCKER.md
- docker-setup.sh
- README.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Changes:
- Add 'local' stage as alias for 'development' in Dockerfile
- Restructure Dockerfile for proper dev/prod separation
- Development stage: lightweight, no built assets, OPcache disabled
- Production stage: optimized, pre-built assets, OPcache enabled
- Update docker-setup.sh to install dependencies for local dev
- Add composer install and npm install steps for local env
- Add proper permissions setup in script
- Update documentation with build stages info

Now works correctly with APP_ENV=local for development and
APP_ENV=production for production deployments.

For local development:
- Code changes are reflected immediately (volume mount)
- Dependencies installed in container
- OPcache disabled for development
- Dev dependencies included

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update MySQL port from 3306 to 3313 to prevent conflicts with
local MySQL installations.

Updated in:
- docker-compose.yml (port mapping)
- .env.docker (default DB_PORT)
- DOCKER.md (documentation)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove deprecated 'default_authentication_plugin' setting that was
removed in MySQL 8.0. This was causing MySQL container to fail on startup.

MySQL 8 uses caching_sha2_password by default, which works fine with
modern PHP/Laravel installations.

Fixes error: unknown variable 'default_authentication_plugin'

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Changes:
- Upgrade Dockerfile from PHP 8.2 to PHP 8.3 (required by dependencies)
- Note: .env file ports need manual update after first setup

The composer.json requires PHP >= 8.3.0

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Critical fix: DB_PORT should be 3306 (internal) not 3313 (external)

Inside Docker containers, services communicate using internal ports:
- MySQL internal: 3306 (used by Laravel app)
- MySQL external: 3313 (for host machine access)

Updated:
- .env.docker: DB_PORT=3306 (correct for container networking)
- DOCKER.md: Added clarification about internal vs external ports

External port 3313 is only for connecting from host machine
tools like MySQL Workbench, TablePlus, etc.

This fixes: Connection refused errors when app tries to connect to MySQL

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@alimranahmed
Copy link
Copy Markdown
Owner Author

@copilot resolve the merge conflicts in this pull request

…-docker-support

# Conflicts:
#	package-lock.json

Co-authored-by: alimranahmed <7629427+alimranahmed@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 14, 2026

@copilot resolve the merge conflicts in this pull request

Merge conflicts resolved in commit 864a4b3. The only conflict was in package-lock.json — the PR branch uses newer package versions (Tailwind v4, Vite v7) that already supersede the security patches merged into main, so the PR branch's lock file was retained.

@alimranahmed alimranahmed merged commit 465a559 into main Apr 14, 2026
1 check failed
@alimranahmed alimranahmed deleted the imran/feature/202-add-docker-support branch April 14, 2026 00:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Docker support for local development and deployment

3 participants