Docker Compose로 Laravel 어플리케이션 개발·배포하기

Docker Compose로 Laravel 어플리케이션 개발·배포하기

이 가이드는 Docker Compose를 사용해 Laravel 개발·프로덕션 환경을 효율적으로 구성하는 방법을 알려줘요.

출처: 문서

본문

드러보는 인기 있는 PHP 프레임워크로, 개발자가 웹 어플리케이션을 빠르고 효과적으로 만들 수 있게 해줘요. Docker Compose는 PHP, 웹 서버, 데이터베이스 같은 필수 서비스를 하나의 YAML 파일로 정의해 개발·프로덕션 환경 관리를 단순화해요. 이 가이드는 간결함과 효율성에 초점을 맞춰 Docker Compose로 견고한 Laravel 환경을 구성하는 간소화된 방법을 제공해요.

감사의 말 (Acknowledgment)

Docker는 이 가이드에 기여해준 Sergei Shitikov에게 감사를 전해요.

설명된 예제는 이 GitHub 저장소에서 찾을 수 있어요. Docker Compose는 Laravel용으로 여러 컨테이너를 연결하는 간단명료한 방법을 제공하지만, Docker Swarm, Kubernetes, 개별 Docker 컨테이너 같은 도구로도 비슷한 구성을 만들 수 있어요.

이 가이드는 교육 목적으로 작성됐으며, 개발자가 자신의 특정 사용 사례에 맞게 구성을 수정·최적화하도록 도와줘요. 또한 컨테이너에서 Laravel을 지원하는 기존 도구도 있어요:

  • Laravel Sail: Docker에서 Laravel을 쉽게 시작하기 위한 공식 패키지.
  • Laradock: Docker에서 Laravel 어플리케이션을 실행하도록 도와주는 커뮤니티 프로젝트.

무엇을 배우게 될까요?

  • Docker Compose를 사용해 Laravel 개발·프로덕션 환경을 구성하는 방법.
  • PHP-FPM, Nginx, 데이터베이스 컨테이너를 포함해 Laravel 개발을 더 쉽게 만드는 서비스 정의하기.
  • 컨테이너화를 사용해 Laravel 환경을 관리하는 모범 사례.

이 가이드는 누구를 위한 것인가요?

  • Laravel로 작업하면서 환경 관리를 간소화하고 싶은 개발자.
  • Laravel 어플리케이션을 효율적으로 관리·배포하는 방법을 찾는 DevOps 엔지니어.

Docker Compose로 Laravel을 구성하기 위한 준비 사항 (Prerequisites)

Docker Compose로 Laravel 구성을 시작하기 전에, 다음 준비 사항을 충족하는지 확인해요.

Docker와 Docker Compose

시스템에 Docker와 Docker Compose가 설치되어 있어야 해요. Docker는 어플리케이션을 컨테이너화하게 해주고, Docker Compose는 다중 컨테이너 어플리케이션을 관리하게 해줘요.

  • Docker: 머신에 Docker가 설치되어 실행 중인지 확인해요. Docker를 설치하려면 Docker 설치 가이드를 참고해요.
  • Docker Compose: Docker Compose는 Docker Desktop에 포함되어 있지만, 필요하다면 Docker Compose 설치 가이드를 따라도 돼요.

Docker와 컨테이너에 대한 기본 이해

Docker와 컨테이너가 어떻게 동작하는지에 대한 기본적인 이해가 도움이 될 거예요. Docker가 처음이라면 Docker Overview를 검토해 컨테이너화 개념에 익숙해져 보는 걸 고려해요.

Laravel에 대한 기본 지식

이 가이드는 Laravel과 PHP에 대한 기본적인 이해가 있다고 가정해요. Artisan 같은 Laravel의 명령줄 도구와 프로젝트 구조에 익숙한 것이 지시사항을 따라가는 데 중요해요.

  • Laravel CLI: Laravel의 명령줄 도구(artisan)를 편안하게 사용할 수 있어야 해요.
  • Laravel 프로젝트 구조: Laravel의 폴더 구조(app, config, routes, tests 등)에 익숙해져요.

Docker Compose로 Laravel 프로덕션 구성하기

이 가이드는 Docker와 Docker Compose를 사용해 프로덕션 준비가 된 Laravel 환경을 구성하는 방법을 보여줘요. 이 구성은 간소화되고 확장 가능하며 안전한 Laravel 어플리케이션 배포를 위해 설계됐어요.

Note

바로 실행 가능한 구성을 실험해보려면 Laravel Docker Examples 저장소를 다운로드해요. 개발용과 프로덕션용 사전 구성이 모두 들어 있어요.

프로젝트 구조

my-laravel-app/
├── app/
├── bootstrap/
├── config/
├── database/
├── public/
├── docker/
│   ├── common/
│   │   └── php-fpm/
│   │       ├── Dockerfile
│   │       └── conf.d/
│   │           └── 20-status-path.conf
│   ├── development/
│   ├── production/
│   │   ├── php-fpm/
│   │   │   └── entrypoint.sh
│   │   └── nginx
│   │       ├── Dockerfile
│   │       └── nginx.conf
├── compose.dev.yaml
├── compose.prod.yaml
├── .dockerignore
├── .env
├── vendor/
├── ...

이 레이아웃은 전형적인 Laravel 프로젝트를 나타내며, Docker 구성은 통합된 docker 디렉토리에 저장돼요. 두 개의 Compose 파일 — compose.dev.yaml(개발용)과 compose.prod.yaml(프로덕션용) — 을 찾을 수 있는데, 이렇게 하면 환경을 분리하고 관리하기 쉽게 유지할 수 있어요.

PHP-FPM용 Dockerfile 만들기 (프로덕션)

프로덕션에서 php-fpm Dockerfile은 어플리케이션이 필요로 하는 PHP 확장과 라이브러리만 담은 최적화된 이미지를 만들어요. GitHub 예제에서 보여주듯이 다단계 빌드(multi-stage builds)를 사용한 하나의 Dockerfile은 개발과 프로덕션 사이의 일관성을 유지하고 중복을 줄여줘요. 다음 스니펫은 프로덕션 관련 스테이지만 보여줘요:

# Stage 1: Build environment and Composer dependencies
FROM php:8.5-fpm AS builder

# Install system dependencies and PHP extensions for Laravel with MySQL/PostgreSQL support.
# Dependencies in this stage are only required for building the final image.
# Node.js and asset building are handled in the Nginx stage, not here.
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    unzip \
    libpq-dev \
    libonig-dev \
    libssl-dev \
    libxml2-dev \
    libcurl4-openssl-dev \
    libicu-dev \
    libzip-dev \
    && docker-php-ext-install -j$(nproc) \
    pdo_mysql \
    pdo_pgsql \
    pgsql \
    intl \
    zip \
    bcmath \
    soap \
    && pecl install redis \
    && docker-php-ext-enable redis \
    && apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Set the working directory inside the container
WORKDIR /var/www

# Copy the entire Laravel application code into the container
# -----------------------------------------------------------
# In Laravel, `composer install` may trigger scripts
# needing access to application code.
# For example, the `post-autoload-dump` event might execute
# Artisan commands like `php artisan package:discover`. If the
# application code (including the `artisan` file) is not
# present, these commands will fail, leading to build errors.
#
# By copying the entire application code before running
# `composer install`, we ensure that all necessary files are
# available, allowing these scripts to run successfully.
# In other cases, it would be possible to copy composer files
# first, to leverage Docker's layer caching mechanism.
# -----------------------------------------------------------
COPY . /var/www

# Install Composer and dependencies
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
    && composer install --no-dev --optimize-autoloader --no-interaction --no-progress --prefer-dist

# Stage 2: Production environment
FROM php:8.5-fpm AS production

# Install only runtime libraries needed in production
# libfcgi-bin and procps are required for the php-fpm-healthcheck script
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq-dev \
    libicu-dev \
    libzip-dev \
    libfcgi-bin \
    procps \
    && apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Download and install php-fpm health check script
RUN curl -o /usr/local/bin/php-fpm-healthcheck \
    https://raw.githubusercontent.com/renatomefi/php-fpm-healthcheck/master/php-fpm-healthcheck \
    && chmod +x /usr/local/bin/php-fpm-healthcheck

# Copy the initialization script
COPY ./docker/php-fpm/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

# Copy the initial storage structure
COPY ./storage /var/www/storage-init

# Copy PHP extensions and libraries from the builder stage
COPY --from=builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
COPY --from=builder /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/
COPY --from=builder /usr/local/bin/docker-php-ext-* /usr/local/bin/

# Use the recommended production PHP configuration
# -----------------------------------------------------------
# PHP provides development and production configurations.
# Here, we replace the default php.ini with the production
# version to apply settings optimized for performance and
# security in a live environment.
# -----------------------------------------------------------
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"

# Keep the image-provided FPM global config intact and add pool overrides separately
COPY ./docker/common/php-fpm/conf.d/*.conf /usr/local/etc/php-fpm.d/
# Update the variables_order to include E (for ENV)
#RUN sed -i 's/variables_order = "GPCS"/variables_order = "EGPCS"/' "$PHP_INI_DIR/php.ini"

# Copy the application code and dependencies from the build stage
COPY --from=builder /var/www /var/www

# Set working directory
WORKDIR /var/www

# Ensure correct permissions
RUN chown -R www-data:www-data /var/www

# Switch to the non-privileged user to run the application
USER www-data

# Change the default command to run the entrypoint script
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

# Expose port 9000 and start php-fpm server
EXPOSE 9000
CMD ["php-fpm"]

PHP-CLI용 Dockerfile 만들기 (프로덕션)

프로덕션에서는 Artisan 명령, 마이그레이션, 기타 CLI 태스크를 실행하기 위해 별도의 컨테이너가 필요한 경우가 많아요. 대부분의 경우 기존 PHP-FPM 컨테이너를 재사용해 이런 명령을 실행할 수 있어요:

$ docker compose -f compose.prod.yaml exec php-fpm php artisan route:list

다른 확장이 필요하거나 관심사 분리가 엄격해야 한다면 php-cli Dockerfile을 고려해볼 수 있어요:

# Stage 1: Build environment and Composer dependencies
FROM php:8.5-cli AS builder

# Install system dependencies and PHP extensions required for Laravel + MySQL/PostgreSQL support
# Some dependencies are required for PHP extensions only in the build stage
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    unzip \
    libpq-dev \
    libonig-dev \
    libssl-dev \
    libxml2-dev \
    libcurl4-openssl-dev \
    libicu-dev \
    libzip-dev \
    && docker-php-ext-install -j$(nproc) \
    pdo_mysql \
    pdo_pgsql \
    pgsql \
    intl \
    zip \
    bcmath \
    soap \
    && pecl install redis \
    && docker-php-ext-enable redis \
    && apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Set the working directory inside the container
WORKDIR /var/www

# Copy the entire Laravel application code into the container
COPY . /var/www

# Install Composer and dependencies
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
    && composer install --no-dev --optimize-autoloader --no-interaction --no-progress --prefer-dist

# Stage 2: Production environment
FROM php:8.5-cli

# Install client libraries required for php extensions in runtime
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq-dev \
    libicu-dev \
    libzip-dev \
    && apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Copy PHP extensions and libraries from the builder stage
COPY --from=builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
COPY --from=builder /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/
COPY --from=builder /usr/local/bin/docker-php-ext-* /usr/local/bin/

# Use the default production configuration for PHP runtime arguments
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"

# Copy the application code and dependencies from the build stage
COPY --from=builder /var/www /var/www

# Set working directory
WORKDIR /var/www

# Ensure correct permissions
RUN chown -R www-data:www-data /var/www

# Switch to the non-privileged user to run the application
USER www-data

# Default command: Provide a bash shell to allow running any command
CMD ["bash"]

이 Dockerfile은 PHP-FPM Dockerfile과 비슷하지만, php:8.5-cli 이미지를 베이스 이미지로 사용해 CLI 명령을 실행하는 컨테이너를 구성해요.

Nginx용 Dockerfile 만들기 (프로덕션)

Nginx는 Laravel 어플리케이션의 웹 서버 역할을 해요. 정적 자산을 컨테이너에 직접 포함할 수 있어요. 다음은 Nginx용 가능한 Dockerfile 예시예요:

# docker/nginx/Dockerfile
# Stage 1: Build assets
FROM debian AS builder

# Install Node.js and build tools
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    nodejs \
    npm \
    && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Set working directory
WORKDIR /var/www

# Copy Laravel application code
COPY . /var/www

# Install Node.js dependencies and build assets
RUN npm install && npm run build

# Stage 2: Nginx production image
FROM nginx:alpine

# Copy custom Nginx configuration
# -----------------------------------------------------------
# Replace the default Nginx configuration with our custom one
# that is optimized for serving a Laravel application.
# -----------------------------------------------------------
COPY ./docker/nginx/nginx.conf /etc/nginx/nginx.conf

# Copy Laravel's public assets from the builder stage
# -----------------------------------------------------------
# We only need the 'public' directory from our Laravel app.
# -----------------------------------------------------------
COPY --from=builder /var/www/public /var/www/public

# Set the working directory to the public folder
WORKDIR /var/www/public

# Expose port 80 and start Nginx
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

이 Dockerfile은 다단계 빌드를 사용해 자산 빌드 과정을 최종 프로덕션 이미지와 분리해요. 첫 번째 스테이지는 Node.js를 설치하고 자산을 빌드하며, 두 번째 스테이지는 최적화된 구성과 빌드된 자산으로 Nginx 프로덕션 이미지를 구성해요.

프로덕션용 Docker Compose 구성 만들기

모든 서비스를 함께 묶으려면 프로덕션 환경의 서비스, 볼륨, 네트워크를 정의하는 compose.prod.yaml 파일을 만들어요. 다음은 예시 구성이에요:

services:
  web:
    build:
      context: .
      dockerfile: ./docker/production/nginx/Dockerfile
    restart: unless-stopped # Automatically restart unless the service is explicitly stopped
    volumes:
      # Mount the 'laravel-storage' volume to '/var/www/storage' inside the container.
      # -----------------------------------------------------------
      # This volume stores persistent data like uploaded files and cache.
      # The ':ro' option mounts it as read-only in the 'web' service because Nginx only needs to read these files.
      # The 'php-fpm' service mounts the same volume without ':ro' to allow write operations.
      # -----------------------------------------------------------
      - laravel-storage-production:/var/www/storage:ro
    networks:
      - laravel-production
    ports:
      # Map port 80 inside the container to the port specified by 'NGINX_PORT' on the host machine.
      # -----------------------------------------------------------
      # This allows external access to the Nginx web server running inside the container.
      # For example, if 'NGINX_PORT' is set to '8080', accessing 'http://localhost:8080' will reach the application.
      # -----------------------------------------------------------
      - "${NGINX_PORT:-80}:80"
    depends_on:
      php-fpm:
        condition: service_healthy # Wait for php-fpm health check

  php-fpm:
    # For the php-fpm service, we will create a custom image to install the necessary PHP extensions and setup proper permissions.
    build:
      context: .
      dockerfile: ./docker/common/php-fpm/Dockerfile
      target: production # Use the 'production' stage in the Dockerfile
    restart: unless-stopped
    volumes:
      - laravel-storage-production:/var/www/storage # Mount the storage volume
    env_file:
      - .env
    networks:
      - laravel-production
    healthcheck:
      test: ["CMD-SHELL", "php-fpm-healthcheck || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 3
    # The 'depends_on' attribute with 'condition: service_healthy' ensures that
    # this service will not start until the 'postgres' service passes its health check.
    # This prevents the application from trying to connect to the database before it's ready.
    depends_on:
      postgres:
        condition: service_healthy

  # The 'php-cli' service provides a command-line interface for running Artisan commands and other CLI tasks.
  # -----------------------------------------------------------
  # This is useful for running migrations, seeders, or any custom scripts.
  # It shares the same codebase and environment as the 'php-fpm' service.
  # -----------------------------------------------------------
  php-cli:
    build:
      context: .
      dockerfile: ./docker/php-cli/Dockerfile
    tty: true # Enables an interactive terminal
    stdin_open: true # Keeps standard input open for 'docker exec'
    env_file:
      - .env
    networks:
      - laravel-production

  postgres:
    image: postgres:18
    restart: unless-stopped
    user: postgres
    ports:
      - "${POSTGRES_PORT}:5432"
    environment:
      - POSTGRES_DB=${POSTGRES_DATABASE}
      - POSTGRES_USER=${POSTGRES_USERNAME}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - postgres-data-production:/var/lib/postgresql
    networks:
      - laravel-production
    # Health check for PostgreSQL
    # -----------------------------------------------------------
    # Health checks allow Docker to determine if a service is operational.
    # The 'pg_isready' command checks if PostgreSQL is ready to accept connections.
    # This prevents dependent services from starting before the database is ready.
    # -----------------------------------------------------------
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:alpine
    restart: unless-stopped # Automatically restart unless the service is explicitly stopped
    networks:
      - laravel-production
    # Health check for Redis
    # -----------------------------------------------------------
    # Checks if Redis is responding to the 'PING' command.
    # This ensures that the service is not only running but also operational.
    # -----------------------------------------------------------
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3

networks:
  # Attach the service to the 'laravel-production' network.
  # -----------------------------------------------------------
  # This custom network allows all services within it to communicate using their service names as hostnames.
  # For example, 'php-fpm' can connect to 'postgres' by using 'postgres' as the hostname.
  # -----------------------------------------------------------
  laravel-production:

volumes:
  postgres-data-production:
  laravel-storage-production:

Note

Docker Compose 구성과 일치하도록 Laravel 프로젝트 루트에 필요한 구성이 담긴 .env 파일이 있는지 확인해요.

프로덕션 환경 실행하기

프로덕션 환경을 시작하려면 다음을 실행해요:

$ docker compose -f compose.prod.yaml up --build -d

이 명령은 모든 서비스를 detached 모드로 빌드·시작해 Laravel 어플리케이션을 위한 확장 가능하고 프로덕션 준비가 된 구성을 제공해요.

요약 (Summary)

Laravel을 위한 Docker Compose 환경을 프로덕션으로 구성하면 어플리케이션이 성능에 최적화되고 확장 가능하며 안전함을 보장할 수 있어요. 이 구성은 배포를 일관되고 관리하기 쉽게 만들어 환경 간 차이로 인한 오류 가능성을 줄여줘요.

Docker Compose로 Laravel 개발 구성하기

이 가이드는 Docker와 Docker Compose를 사용해 Laravel 어플리케이션을 위한 개발 환경을 구성하는 방법을 보여줘요. PHP-FPM용 프로덕션 이미지 위에 구축하고, Xdebug 같은 개발자 중심 기능을 추가해 디버깅을 간소화해요. 개발 컨테이너를 알려진 프로덕션 이미지에 기반해 구성하면 두 환경을 밀접하게 일치시킬 수 있어요.

이 구성에는 PHP-FPM, Nginx, PostgreSQL 서비스가 포함돼요(PostgreSQL을 MySQL이나 MariaDB 같은 다른 데이터베이스로 쉽게 바꿀 수 있어요). 모든 것이 컨테이너에서 실행되므로 호스트 시스템을 변경하지 않고 격리된 상태로 개발할 수 있어요.

Note

바로 실행 가능한 구성을 실험해보려면 Laravel Docker Examples 저장소를 다운로드해요. 개발용과 프로덕션용 사전 구성이 모두 들어 있어요.

프로젝트 구조

my-laravel-app/
├── app/
├── bootstrap/
├── config/
├── database/
├── public/
├── docker/
│   ├── common/
│   │   └── php-fpm/
│   │       └── Dockerfile
│   ├── development/
│   │   ├── php-fpm/
│   │   │   └── entrypoint.sh
│   │   ├── workspace/
│   │   │   └── Dockerfile
│   │   └── nginx
│   │       ├── Dockerfile
│   │       └── nginx.conf
│   └── production/
├── compose.dev.yaml
├── compose.prod.yaml
├── .dockerignore
├── .env
├── vendor/
├── ...

이 레이아웃은 전형적인 Laravel 프로젝트를 나타내며, Docker 구성은 통합된 docker 디렉토리에 저장돼요. 두 개의 Compose 파일 — compose.dev.yaml(개발용)과 compose.prod.yaml(프로덕션용) — 을 찾을 수 있는데, 이렇게 하면 환경을 분리하고 관리하기 쉽게 유지할 수 있어요.

이 환경에는 workspace 서비스가 포함되는데, 프론트엔드 자산 빌드, Artisan 명령 실행, 프로젝트가 필요로 하는 기타 CLI 도구 같은 작업을 위한 사이드카(sidecar) 컨테이너예요. 이 추가 컨테이너가 드물어 보일 수 있지만, Laravel Sail과 Laradock 같은 솔루션에서 익숙한 패턴이에요. 디버깅을 돕는 Xdebug도 포함돼요.

PHP-FPM용 Dockerfile 만들기

이 Dockerfile은 Xdebug를 설치하고 로컬 개발을 용이하게 하도록 사용자 권한을 조정해 프로덕션 이미지를 확장해요. 그렇게 하면 개발 환경이 프로덕션과 일관성을 유지하면서도 추가 디버그 기능과 개선된 파일 마운트를 제공받을 수 있어요.

# Builds a dev-only layer on top of the production image
FROM production AS development

# Use ARGs to define environment variables passed from the Docker build command or Docker Compose.
ARG XDEBUG_ENABLED=true
ARG XDEBUG_MODE=develop,coverage,debug,profile
ARG XDEBUG_HOST=host.docker.internal
ARG XDEBUG_IDE_KEY=DOCKER
ARG XDEBUG_LOG=/dev/stdout
ARG XDEBUG_LOG_LEVEL=0

USER root

# Configure Xdebug if enabled
RUN if [ "${XDEBUG_ENABLED}" = "true" ]; then \
    pecl install xdebug && \
    docker-php-ext-enable xdebug && \
    echo "xdebug.mode=${XDEBUG_MODE}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.idekey=${XDEBUG_IDE_KEY}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.log=${XDEBUG_LOG}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.log_level=${XDEBUG_LOG_LEVEL}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.client_host=${XDEBUG_HOST}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini ; \
    echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini ; \
fi

# Add ARGs for syncing permissions
ARG UID=1000
ARG GID=1000

# Create a new user with the specified UID and GID, reusing an existing group if GID exists
RUN if getent group ${GID}; then \
      group_name=$(getent group ${GID} | cut -d: -f1); \
      useradd -m -u ${UID} -g ${GID} -s /bin/bash www; \
    else \
      groupadd -g ${GID} www && \
      useradd -m -u ${UID} -g www -s /bin/bash www; \
      group_name=www; \
    fi

# Dynamically update php-fpm to use the new user and group
RUN sed -i "s/user = www-data/user = www/g" /usr/local/etc/php-fpm.d/www.conf && \
    sed -i "s/group = www-data/group = $group_name/g" /usr/local/etc/php-fpm.d/www.conf

# Set the working directory
WORKDIR /var/www

# Copy the entrypoint script
COPY ./docker/development/php-fpm/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

# Switch back to the non-privileged user to run the application
USER www-data

# Change the default command to run the entrypoint script
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

# Expose port 9000 and start php-fpm server
EXPOSE 9000
CMD ["php-fpm"]

Workspace용 Dockerfile 만들기

workspace 컨테이너는 자산 컴파일, Artisan/Composer 명령, 기타 CLI 태스크를 위한 전용 셸을 제공해요. 이 접근 방식은 Laravel Sail과 Laradock의 패턴을 따르며, 모든 개발 도구를 편의를 위해 하나의 컨테이너로 통합해요.

# docker/development/workspace/Dockerfile
# Use the official PHP CLI image as the base
FROM php:8.5-cli

# Set environment variables for user and group ID
ARG UID=1000
ARG GID=1000
ARG NODE_VERSION=22.0.0

# Install system dependencies and build libraries
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    unzip \
    libpq-dev \
    libonig-dev \
    libssl-dev \
    libxml2-dev \
    libcurl4-openssl-dev \
    libicu-dev \
    libzip-dev \
    && docker-php-ext-install -j$(nproc) \
    pdo_mysql \
    pdo_pgsql \
    pgsql \
    intl \
    zip \
    bcmath \
    soap \
    && pecl install redis \
    && docker-php-ext-enable redis \
    && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
    && apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Use ARG to define environment variables passed from the Docker build command or Docker Compose.
ARG XDEBUG_ENABLED
ARG XDEBUG_MODE
ARG XDEBUG_HOST
ARG XDEBUG_IDE_KEY
ARG XDEBUG_LOG
ARG XDEBUG_LOG_LEVEL

# Configure Xdebug if enabled
RUN if [ "${XDEBUG_ENABLED}" = "true" ]; then \
    pecl install xdebug && \
    docker-php-ext-enable xdebug && \
    echo "xdebug.mode=${XDEBUG_MODE}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.idekey=${XDEBUG_IDE_KEY}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.log=${XDEBUG_LOG}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.log_level=${XDEBUG_LOG_LEVEL}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.client_host=${XDEBUG_HOST}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini ; \
    echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini ; \
fi

# If the group already exists, use it; otherwise, create the 'www' group
RUN if getent group ${GID}; then \
      useradd -m -u ${UID} -g ${GID} -s /bin/bash www; \
    else \
      groupadd -g ${GID} www && \
      useradd -m -u ${UID} -g www -s /bin/bash www; \
    fi && \
    usermod -aG sudo www && \
    echo 'www ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

# Switch to the non-root user to install NVM and Node.js
USER www

# Install NVM (Node Version Manager) as the www user
RUN export NVM_DIR="$HOME/.nvm" && \
    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash && \
    [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && \
    nvm install ${NODE_VERSION} && \
    nvm alias default ${NODE_VERSION} && \
    nvm use default

# Ensure NVM is available for all future shells
RUN echo 'export NVM_DIR="$HOME/.nvm"' >> /home/www/.bashrc && \
    echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> /home/www/.bashrc && \
    echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"' >> /home/www/.bashrc

# Set the working directory
WORKDIR /var/www

# Override the entrypoint to avoid the default php entrypoint
ENTRYPOINT []

# Default command to keep the container running
CMD ["bash"]

Note

서비스당 하나의 컨테이너 방식이 더 좋다면 workspace 컨테이너를 생략하고 각 태스크를 위해 별도의 컨테이너를 실행해도 돼요. 예를 들어 PHP 스크립트용으로 전용 php-cli 컨테이너를, 자산 빌드를 처리하는 node 컨테이너를 사용할 수 있어요.

개발용 Docker Compose 구성 만들기

개발 환경을 구성하는 compose.yaml 파일은 다음과 같아요:

services:
  web:
    image: nginx:latest # Using the default Nginx image with custom configuration.
    volumes:
      # Mount the application code for live updates
      - ./:/var/www
      # Mount the Nginx configuration file
      - ./docker/development/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    ports:
      # Map port 80 inside the container to the port specified by 'NGINX_PORT' on the host machine
      - "80:80"
    environment:
      - NGINX_HOST=localhost
    networks:
      - laravel-development
    depends_on:
      php-fpm:
        condition: service_started # Wait for php-fpm to start

  php-fpm:
    # For the php-fpm service, we will use our common PHP-FPM Dockerfile with the development target
    build:
      context: .
      dockerfile: ./docker/common/php-fpm/Dockerfile
      target: development
      args:
        UID: ${UID:-1000}
        GID: ${GID:-1000}
        XDEBUG_ENABLED: ${XDEBUG_ENABLED:-true}
        XDEBUG_MODE: develop,coverage,debug,profile
        XDEBUG_HOST: ${XDEBUG_HOST:-host.docker.internal}
        XDEBUG_IDE_KEY: ${XDEBUG_IDE_KEY:-DOCKER}
        XDEBUG_LOG: /dev/stdout
        XDEBUG_LOG_LEVEL: 0
    env_file:
      # Load the environment variables from the Laravel application
      - .env
    user: "${UID:-1000}:${GID:-1000}"
    volumes:
      # Mount the application code for live updates
      - ./:/var/www
    networks:
      - laravel-development
    depends_on:
      postgres:
        condition: service_started # Wait for postgres to start

  workspace:
    # For the workspace service, we will also create a custom image to install and setup all the necessary stuff.
    build:
      context: .
      dockerfile: ./docker/development/workspace/Dockerfile
      args:
        UID: ${UID:-1000}
        GID: ${GID:-1000}
        XDEBUG_ENABLED: ${XDEBUG_ENABLED:-true}
        XDEBUG_MODE: develop,coverage,debug,profile
        XDEBUG_HOST: ${XDEBUG_HOST:-host.docker.internal}
        XDEBUG_IDE_KEY: ${XDEBUG_IDE_KEY:-DOCKER}
        XDEBUG_LOG: /dev/stdout
        XDEBUG_LOG_LEVEL: 0
    tty: true # Enables an interactive terminal
    stdin_open: true # Keeps standard input open for 'docker exec'
    env_file:
      - .env
    volumes:
      - ./:/var/www
    networks:
      - laravel-development

  postgres:
    image: postgres:18
    ports:
      - "${POSTGRES_PORT:-5432}:5432"
    environment:
      - POSTGRES_DB=app
      - POSTGRES_USER=laravel
      - POSTGRES_PASSWORD=secret
    volumes:
      - postgres-data-development:/var/lib/postgresql
    networks:
      - laravel-development

  redis:
    image: redis:alpine
    networks:
      - laravel-development

networks:
  laravel-development:

volumes:
  postgres-data-development:

Note

Laravel 프로젝트 루트에 필요한 구성이 담긴 .env 파일이 있는지 확인해요. .env.example 파일을 템플릿으로 사용할 수 있어요.

개발 환경 실행하기

개발 환경을 시작하려면 다음을 사용해요:

$ docker compose -f compose.dev.yaml up --build -d

이 명령을 실행해 개발 환경을 detached 모드로 빌드·시작해요. 컨테이너 초기화가 끝나면 http://localhost/를 방문해 Laravel 앱을 확인해요.

요약 (Summary)

프로덕션 이미지 위에 구축하고 Xdebug 같은 디버그 도구를 추가하면 프로덕션을 밀접하게 반영하는 Laravel 개발 워크플로를 만들 수 있어요. 선택적인 workspace 컨테이너는 자산 빌드와 Artisan 명령 실행 같은 작업을 단순화해요. 서비스마다 별도의 컨테이너를 선호한다면(예: 전용 php-cli와 node 컨테이너) workspace 방식을 건너뛰어도 돼요. 어느 쪽이든 Docker Compose는 Laravel 프로젝트를 개발할 수 있는 효율적이고 일관된 방법을 제공해요.

Laravel을 Docker와 함께 사용할 때의 일반적인 질문 (Common Questions)

1. Laravel에 Docker Compose를 왜 사용해야 하나요?

Docker Compose는 다중 컨테이너 환경을 관리하기 위한 강력한 도구로, 특히 개발에서 그 단순함 덕분에 유용해요. Docker Compose를 사용하면 Laravel에 필요한 모든 서비스(PHP, Nginx, 데이터베이스 등)를 하나의 구성(compose.*.yaml)으로 정의하고 연결할 수 있어요. 이 구성은 개발, 테스트, 프로덕션 환경 전반에서 일관성을 보장해 온보딩을 간소화하고 로컬과 서버 구성 사이의 차이를 줄여줘요.

Docker Compose는 개발에 훌륭한 선택이지만, Docker Swarm이나 Kubernetes 같은 도구는 고급 확장·오케스트레이션 기능을 제공하므로 복잡한 프로덕션 배포에 유용할 수 있어요.

2. Docker Compose로 Laravel 어플리케이션을 어떻게 디버깅하나요?

Docker 환경에서 Laravel 어플리케이션을 디버깅하려면 Xdebug를 사용해요. 개발 구성에서 Xdebug는 php-fpm 컨테이너에 설치되어 디버깅을 활성화해요. compose.dev.yaml 파일에서 환경 변수 XDEBUG_ENABLED=true를 설정하고 IDE(예: Visual Studio Code나 PHPStorm)를 구성해 원격 컨테이너에 연결해 디버깅을 하도록 설정했는지 확인해요.

3. PostgreSQL 외의 데이터베이스와도 Docker Compose를 사용할 수 있나요?

네, Docker Compose는 Laravel을 위한 다양한 데이터베이스 서비스를 지원해요. 예제에서는 PostgreSQL을 사용하지만 MySQL, MariaDB, 심지어 SQLite로도 쉽게 대체할 수 있어요. compose.*.yaml 파일을 업데이트해 필요한 Docker 이미지를 지정하고, .env 파일을 새 데이터베이스 구성에 맞게 조정해요.

4. 개발과 프로덕션에서 데이터를 어떻게 유지(Persist)하나요?

개발과 프로덕션 모두에서 Docker 볼륨을 사용해 데이터를 유지해요. 예를 들어 compose.*.yaml 파일에서 postgres-data-* 볼륨은 PostgreSQL 데이터를 저장해 컨테이너가 재시작되더라도 데이터가 보존되도록 해요. 데이터 유지가 필수적인 다른 서비스에도 명명된 볼륨을 정의할 수 있어요.

5. 개발과 프로덕션 Docker 구성의 차이는 무엇인가요?

개발 환경에서 Docker 구성은 Xdebug 같은 디버깅 도구와, 이미지 리빌드 없이 실시간 코드 업데이트를 가능하게 하는 볼륨 마운트처럼 코딩과 디버깅을 간소화하는 도구를 포함해요.

프로덕션에서는 구성이 성능, 보안, 효율성에 최적화돼요. 이 구성은 다단계 빌드를 사용해 이미지를 가볍게 유지하고 필수 도구, 패키지, 라이브러리만 포함해요.

프로덕션에서는 이미지 크기를 줄이고 배포 속도와 보안을 높이기 위해 alpine 기반 이미지를 사용하는 것이 권장돼요.

또한 특히 프로덕션 환경에서 취약점을 탐지·분석하려면 Docker Scout를 사용하는 것을 고려해요.

프로덕션에서 Docker Compose를 사용하는 것에 대한 추가 정보는 이 가이드를 참고해요.

더 알아보기 (Learn more)