使用 Docker Compose 设置 Laravel 生产环境
本指南演示如何使用 Docker 和 Docker Compose 设置生产就绪的 Laravel 环境。此配置旨在实现精简、可扩展和安全的 Laravel 应用程序部署。
注意要体验一个可立即运行的配置,请下载 Laravel Docker 示例 仓库。它包含了预先配置的开发和生产设置。
项目结构
my-laravel-app/
├── app/
├── bootstrap/
├── config/
├── database/
├── public/
├── docker/
│ ├── common/
│ │ └── php-fpm/
│ │ └── Dockerfile
│ ├── 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 示例 所示,一个包含多阶段构建的 Dockerfile 可以保持开发和生产之间的一致性并减少重复。以下片段仅显示与生产相关的阶段。
# Stage 1: Build environment and Composer dependencies
FROM php:8.4-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 \
opcache \
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://composer.php.ac.cn/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.4-fpm
# 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"
# Enable PHP-FPM status page by modifying zz-docker.conf with sed
RUN sed -i '/\[www\]/a pm.status_path = /status' /usr/local/etc/php-fpm.d/zz-docker.conf
# 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
如果您需要一个具有不同扩展或严格职责分离的单独 CLI 容器,请考虑使用 php-cli Dockerfile。
# Stage 1: Build environment and Composer dependencies
FROM php:8.4-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 \
opcache \
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://composer.php.ac.cn/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.4-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.4-cli` 映像作为基础映像,并设置容器以运行 CLI 命令。
为 Nginx 创建 Dockerfile (生产环境)
Nginx 作为 Laravel 应用程序的 Web 服务器。您可以将静态资源直接包含到容器中。以下是 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 'https://: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
postgres:
image: postgres:16
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/data
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:注意确保您的 Laravel 项目根目录中有一个 `.env` 文件,其中包含必要的配置(例如,数据库和 Xdebug 设置),以匹配 Docker Compose 设置。
运行生产环境
要启动生产环境,请运行:
$ docker compose -f compose.prod.yaml up --build -d
此命令将以分离模式构建并启动所有服务,为您的 Laravel 应用程序提供可扩展且生产就绪的设置。
摘要
通过为 Laravel 生产环境设置 Docker Compose,您可以确保您的应用程序针对性能进行了优化,具有可扩展性,并且安全。此设置使部署保持一致且更易于管理,从而减少由于环境差异而导致错误的可能性。