A Simple Docker Setup for Laravel Development

So, Laravel and the TALL stack – my go-to combo for building web apps 🚀. But, let’s be real, setting up a local development environment can sometimes feel like trying to fold a fitted sheet – frustrating and not much fun.

That’s where this blog post comes in. We’ll create a simpe & nifty docker-compose setup that’ll make your Laravel development life a whole lot smoother. Say goodbye to the headaches of environment configuration and hello to more time spent actually building cool stuff. Let’s dive in!

A screenshot of the docker-compose file

Structure

Let’s closely examine the folder organization:

The app folder is designated for your Laravel (or other code!) project. data is reserved for database persistency, particularly if you favor a local volume, just like I do. The config file encompasses all the additional configurations required, including the Dockerfile for PHP. This is also the place where you can house your custom php.ini and www.conf if necessary.

my-app/
├─ docker-compose.yml
├─ app/
├─ data/
├─ config/
│  ├─ caddy/
│  │  ├─ Caddyfile
│  ├─ php/
│  │  ├─ Dockerfile

Dive into the Docker-verse: Your docker-compose.yml Deconstructed

Let’s begin with the docker-compose.yml file. It incorporates all the services essential for you to kickstart your work, primarily the app service, along with redis and mysql. It’s worth noting that you have the flexibility to easily switch between versions or, for instance, substitute MySql with MariaDB.

version: '3.4'

networks:
  app-network:  # Our virtual space for services to chat with each other.

x-app-volume:
  &app-volume  # A volume alias, where we share files between your machine and the container.
  type: bind
  source: ./app
  target: /var/www/html  # The container's web directory.
x-app-dir: &app-dir /var/www/html  # Another handy alias for directory paths.

services:
  app:
    build:
      context: php/
      dockerfile: Dockerfile  # Your app service. Docker knows how to build it.
    tty: true  # Let's keep things chatty in the terminal.
    restart: unless-stopped  # The app restarts only when it needs to.
    environment:
      XDEBUG_CONFIG: "client_host=172.17.0.1"
      XDEBUG_MODE: "debug"
      XDEBUG_TRIGGER: "trigger"  # Debugging goodness.
    volumes:
      - *app-volume  # Share your code with the container.
    working_dir: *app-dir  # Work from the container's web directory.
    networks:
      - app-network  # Our app needs a network to play with.

  caddy:
    image: caddy:2.6.4-alpine  # Caddy's our web server.
    ports:
      - 80:80  # Access your app.
    volumes:
      - *app-volume  # Share code with Caddy.
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile  # Caddy's configuration.
    depends_on:
      - app
      - mysql
    networks:
      - app-network

  mysql:
    image: mysql:8  # Our database.
    restart: unless-stopped
    tty: true
    environment:
      MYSQL_DATABASE: laravel
      MYSQL_USER: laravel
      MYSQL_PASSWORD: laravel
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - ./data:/var/lib/mysql  # Data stays put with this volume.
    ports:
      - 3306:3306  # Database accessibility.
    networks:
      - app-network

  redis:
    image: redis:7  # Redis, if you need it for local development.
    environment:
      REDIS_PASSWORD: laravel
    ports:
      - 6379:6379
    networks:
      - app-network

You can also utilize an .env file containing all the environmental variables you require. For instance:

DB_ROOT=root
DB_DATABASE=laravel
DB_USER=laravel
DB_PASSWORD=laravel
mysql:
  image: mysql:8
  restart: unless-stopped
  tty: true
  environment:
    MYSQL_DATABASE: ${DB_DATABASE}
    MYSQL_USER: ${DB_USER}
    MYSQL_PASSWORD: ${DB_PASSWORD}
    MYSQL_ROOT_PASSWORD: ${DB_ROOT}
  volumes:
    - ./data:/var/lib/mysql
  ports:
    - 3306:3306
  networks:
    - app-network

Inside the Dockerfile: Where the Magic Happens

Now, let’s talk about the Dockerfile. In this streamlined setup, the objective is to have a single container responsible for both the application and its dependencies. This entails including both composer and npm within the same image.

FROM php:8.1-fpm-bullseye

# System dependencies
RUN apt-get update && apt-get install -y \
    git \
    curl \
    ca-certificates \
    gnupg \
    libpng-dev \
    libonig-dev \
    libxml2-dev \
    zip \
    unzip \
    libzip-dev

# Node
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
ENV NODE_MAJOR=20
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt-get update && apt-get install -y nodejs
# Moving the cache to somewhere you have permission to write
ENV NPM_CONFIG_CACHE=/tmp/.npm

# Composer
COPY --from=composer:2.2.4 /usr/bin/composer /usr/local/bin/composer

# PHP extensions
RUN pecl install redis xdebug \
    && docker-php-ext-enable redis xdebug \
    && docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip opcache

The Caddyfile: Making Local Development a Breeze

Lastly, the Caddyfile, with HTTPS disabled to simplify our lives during local development. You can easily enable HTTPS by removing the http:// prefix from your site address.

http://localhost {
    root * /var/www/html/public.

    encode zstd gzip

    php_fastcgi app:9000

    file_server

    tls internal
}

Let’s Get this Party Started: Docker at Your Command!

Now that you’ve got everything neatly set up, it’s time to kickstart your Laravel development playground. Here’s a quick guide on how to make the magic happen:

Starting Your Docker Environment:

docker-compose up -d

With this command, you’re launching your Docker containers in detached mode, meaning they’ll run in the background, leaving you free to continue your coding adventure.

Accessing the App Container:

To access your app container with the same user and group permissions as your host machine and run any composer, npm or artisan commands:

docker-compose exec --user $(id -u):$(id -g) app bash

Conclusion

In conclusion, what we’ve explored here is a straightforward setup tailored for local development – a foundation upon which you can easily build and customize to suit your specific needs. Whether it’s tweaking configurations, adding new services, or refining it for deployment, this Docker-based environment is a place to start to craft the ideal development ecosystem for your projects. 🚀 🎨