← Back to Blog
DevOps

Deploy Laravel with Docker

A complete walkthrough of containerizing a Laravel app — from Dockerfile to a production-ready docker-compose stack with Nginx and MySQL.

Intermediate Docker Laravel DevOps

Every Laravel project eventually hits the same wall: it works perfectly on your machine, but deploying it anywhere else means fighting PHP versions, missing extensions, and a server config nobody remembers writing. Docker removes that entire category of problem by packaging the app together with everything it needs to run.

Why containerize a Laravel app

A container bundles your code, the PHP runtime, and system dependencies into one portable unit. Once it works locally, the same image runs identically in staging and production — no more "it works on my machine."

The goal isn't just running Laravel in a container. It's making the container the single source of truth for how the app runs, everywhere.

The base Dockerfile

Start with a slim PHP-FPM image and layer in only the extensions Laravel actually needs:

FROM php:8.3-fpm-alpine

RUN apk add --no-cache \
    libpng-dev libzip-dev oniguruma-dev \
    && docker-php-ext-install pdo_mysql mbstring zip gd

WORKDIR /var/www
COPY . .

RUN composer install --optimize-autoloader --no-dev

Keep the image lean

Skip anything you don't need at runtime — dev dependencies, test fixtures, and build tools all belong in a separate build stage, not the final image.

Compose: Nginx, PHP-FPM, and MySQL

A single container rarely ships alone. docker-compose.yml ties the app, web server, and database together as one stack:

services:
  app:
    build: .
    volumes:
      - ./:/var/www
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    depends_on:
      - app
  db:
    image: mariadb:11
    environment:
      MYSQL_DATABASE: laravel
      MYSQL_ROOT_PASSWORD: secret

A few lessons from production

  • Run php artisan config:cache during the image build, not at container start — it shaves real time off cold boots.
  • Mount storage and cache as named volumes so redeploys don't wipe uploaded files.
  • Keep secrets out of the image entirely; inject them as environment variables at runtime.

None of this is exotic — it's the same handful of decisions, made deliberately, that turn "it runs in Docker" into "it deploys the same way every time."

RA
Riki Agustia

Full Stack Developer — Laravel, Next.js, Docker, and self-hosted infrastructure.

Let's build something amazing.

Contact Me