在 Docker 容器中运行 Strapi

页面摘要: 本页面引导你在开发和生产环境中通过 Docker 容器运行 Strapi,包括 Dockerfile 示例、Docker Compose 配置以及常见问题的排查。

WARNING

Strapi 不构建任何官方容器镜像。以下说明是作为对社区的善意提供。如果你有任何疑问,请在 Discord 上联系我们。

将 Strapi 容器化可以使运行时环境在不同机器上可复现,并简化用于部署的依赖管理。本页面涵盖为现有的 Strapi 5 项目构建自定义 Docker 镜像,分别为开发和生产环境提供单独的说明、一个故障排查章节,以及一份社区工具和镜像列表。如果你不想自己编写 Dockerfile,请参阅社区工具和镜像以获取封装好的替代方案。

WARNING

开发环境

开发镜像使用 npm run develop,并挂载你的本地源代码以支持热重载。在创建 Dockerfile 之前,先设置 2 个必需的文件:.dockerignore 和 .env。

创建 .dockerignore 文件

.dockerignore 文件可防止本地文件被复制到 Docker 镜像中。如果没有它,你本地的 node_modules 目录会被包含进构建上下文,从而导致架构不匹配(例如 ARM 上的 x64 二进制文件)并增大镜像体积。

在你的 Strapi 项目根目录创建 .dockerignore 文件:

node_modules/
.tmp/
.cache/
.git/
build/
.env

创建 Dockerfile

以下 Dockerfile 可用于为 Strapi 项目构建非生产用的 Docker 镜像。

FROM node:22-alpine
# Installing libvips-dev for sharp compatibility
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev bash vips-dev git
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

WORKDIR /opt/
COPY package.json package-lock.json ./
RUN npm install -g node-gyp
RUN npm config set fetch-retry-maxtimeout 600000 -g && npm ci
ENV PATH=/opt/node_modules/.bin:$PATH

WORKDIR /opt/app
COPY . .
RUN chown -R node:node /opt/app
USER node
EXPOSE 1337
CMD ["npm", "run", "develop"]
可选:预构建管理面板

你可以在 EXPOSE 行之前添加 RUN ["npm", "run", "build"] 来预构建管理面板并加快首次启动速度。这不是必需的,因为 npm run develop 会在 watch 模式下重新构建它。

可选:使用虚拟包减小镜像体积

对于像这个开发镜像这样的单阶段 Dockerfile,你可以使用 --virtual 标志在 npm ci 之后清理构建依赖,从而生成更精简的镜像:

RUN apk add --no-cache --virtual .build-deps \
    build-base gcc autoconf automake zlib-dev libpng-dev bash vips-dev git \
    && npm ci \
    && apk del .build-deps

这在生产 Dockerfile 中不需要,因为它已经通过多阶段构建丢弃了构建依赖。

受限网络的替代基础镜像

如果你的 CI 环境网络访问受限(例如,DNS 限制导致无法从 GitHub 下载 Sharp 预构建二进制文件),请考虑使用 node:22-slim 而非 node:22-alpine。基于 Debian 的 slim 镜像避免了从源码编译 libvips 等原生依赖的需要,而 Sharp 的预构建二进制文件开箱即用:

FROM node:22-slim
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*

这是以略大的镜像换取更少的构建依赖和更少的网络要求。

设置环境变量

在你的 Strapi 项目根目录创建一个 .env 文件。Docker Compose 在启动容器时会自动读取此文件,因此无需在你的 shell 中 export 它们。

要在 Docker 容器中运行 Strapi,需要以下环境变量:

变量名说明
NODE_ENV应用运行的环境。
DATABASE_CLIENT要使用的数据库客户端。
DATABASE_HOST数据库主机。
DATABASE_PORT数据库端口。
DATABASE_NAME数据库名称。
DATABASE_USERNAME数据库用户名。
DATABASE_PASSWORD数据库密码。
JWT_SECRET用于为 Users-Permissions 插件签名 JWT 的密钥。
ADMIN_JWT_SECRET用于为管理面板签名 JWT 的密钥。
APP_KEYS用于签名会话 cookie 的密钥。
API_TOKEN_SALT用于生成 API 令牌的盐。
TRANSFER_TOKEN_SALT用于生成传输令牌的盐。
ENCRYPTION_KEY用于加密数据库中存储的机密(例如通过管理面板配置的提供方凭据)的密钥。

你还可以设置一些可选环境变量。

以下示例包含占位值。在启动容器之前,请将它们替换为你自己的值:

# Server
HOST=0.0.0.0
PORT=1337

# Database
# Use 'mysql' for MySQL or MariaDB, and change DATABASE_PORT to 3306
DATABASE_CLIENT=postgres
DATABASE_HOST=strapiDB
DATABASE_PORT=5432
DATABASE_NAME=strapi
DATABASE_USERNAME=strapi
DATABASE_PASSWORD=strapi

# Secrets
APP_KEYS=toBeModified1,toBeModified2
API_TOKEN_SALT=tobemodified
ADMIN_JWT_SECRET=tobemodified
TRANSFER_TOKEN_SALT=tobemodified
JWT_SECRET=tobemodified
ENCRYPTION_KEY=tobemodified

# Environment
NODE_ENV=development

为数据库添加 Docker Compose

以下 docker-compose.yml 会在共享网络上启动一个数据库容器和一个 Strapi 容器。

NOTE

有关 Docker Compose 及其命令的更多信息,请参阅 Docker Compose 文档。

PostgreSQL

services:
  strapi:
    container_name: strapi
    build: .
    image: strapi:latest
    restart: unless-stopped
    env_file: .env  # All variables from .env are injected into the container
    volumes:
      - ./config:/opt/app/config
      - ./src:/opt/app/src
      - ./package.json:/opt/package.json
      - ./package-lock.json:/opt/package-lock.json
      - ./.env:/opt/app/.env  # Needed because Strapi uses dotenv to read .env in development
      - ./public/uploads:/opt/app/public/uploads
    ports:
      - "1337:1337"
    networks:
      - strapi
    depends_on:
      strapiDB:
        condition: service_healthy

  strapiDB:
    container_name: strapiDB
    # platform: linux/amd64  # Uncomment if you encounter platform errors on Apple Silicon
    restart: unless-stopped
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ${DATABASE_USERNAME}
      POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
      POSTGRES_DB: ${DATABASE_NAME}
    volumes:
      - strapi-data:/var/lib/postgresql/data/
      #- ./data:/var/lib/postgresql/data/ # if you want to use a bind folder
    ports:
      - "5432:5432"  # Exposed for local debugging tools; remove if not needed
    networks:
      - strapi
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DATABASE_USERNAME} -d ${DATABASE_NAME}"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  strapi-data:

networks:
  strapi:
    name: strapi
    driver: bridge

MySQL

services:
  strapi:
    container_name: strapi
    build: .
    image: strapi:latest
    restart: unless-stopped
    env_file: .env  # All variables from .env are injected into the container
    volumes:
      - ./config:/opt/app/config
      - ./src:/opt/app/src
      - ./package.json:/opt/package.json
      - ./package-lock.json:/opt/package-lock.json
      - ./.env:/opt/app/.env  # Needed because Strapi uses dotenv to read .env in development
      - ./public/uploads:/opt/app/public/uploads
    ports:
      - "1337:1337"
    networks:
      - strapi
    depends_on:
      strapiDB:
        condition: service_healthy

  strapiDB:
    container_name: strapiDB
    # platform: linux/amd64  # Uncomment if you encounter platform errors on Apple Silicon
    restart: unless-stopped
    image: mysql:8.4
    environment:
      MYSQL_USER: ${DATABASE_USERNAME}
      MYSQL_ROOT_PASSWORD: ${DATABASE_PASSWORD}
      MYSQL_PASSWORD: ${DATABASE_PASSWORD}
      MYSQL_DATABASE: ${DATABASE_NAME}
    volumes:
      - strapi-data:/var/lib/mysql
      #- ./data:/var/lib/mysql # if you want to use a bind folder
    ports:
      - "3306:3306"  # Exposed for local debugging tools; remove if not needed
    networks:
      - strapi
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  strapi-data:

networks:
  strapi:
    name: strapi
    driver: bridge

MariaDB

services:
  strapi:
    container_name: strapi
    build: .
    image: strapi:latest
    restart: unless-stopped
    env_file: .env  # All variables from .env are injected into the container
    volumes:
      - ./config:/opt/app/config
      - ./src:/opt/app/src
      - ./package.json:/opt/package.json
      - ./package-lock.json:/opt/package-lock.json
      - ./.env:/opt/app/.env  # Needed because Strapi uses dotenv to read .env in development
      - ./public/uploads:/opt/app/public/uploads
    ports:
      - "1337:1337"
    networks:
      - strapi
    depends_on:
      strapiDB:
        condition: service_healthy

  strapiDB:
    container_name: strapiDB
    # platform: linux/amd64  # Uncomment if you encounter platform errors on Apple Silicon
    restart: unless-stopped
    image: mariadb:11.4
    environment:
      MYSQL_USER: ${DATABASE_USERNAME}
      MYSQL_ROOT_PASSWORD: ${DATABASE_PASSWORD}
      MYSQL_PASSWORD: ${DATABASE_PASSWORD}
      MYSQL_DATABASE: ${DATABASE_NAME}
    volumes:
      - strapi-data:/var/lib/mysql
      #- ./data:/var/lib/mysql # if you want to use a bind folder
    ports:
      - "3306:3306"  # Exposed for local debugging tools; remove if not needed
    networks:
      - strapi
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  strapi-data:

networks:
  strapi:
    name: strapi
    driver: bridge

构建并运行

  1. 构建并启动所有容器:

    docker compose up --build
    
  2. 在浏览器中打开 http://localhost:1337/admin 以访问 Strapi 管理面板。

要停止容器,运行 docker compose down。添加 -v 标志可同时移除数据库卷。

生产环境

生产镜像与开发镜像在 3 个关键方面有所不同:它们使用多阶段构建来减小镜像体积,在最终阶段仅安装生产依赖,并运行 npm run start 而非 develop 命令。这确保镜像仅包含服务应用所需的内容,而不包含开发工具或源代码映射。在生产环境中,应在 Strapi 容器前放置一个反向代理(请参阅部署文档)。

创建生产 Dockerfile

以下 Dockerfile.prod 使用多阶段构建。第一阶段安装所有依赖(包括构建步骤所需的 devDependencies)并构建管理面板。第二阶段仅将生产资源复制到最终镜像中。

WARNING

不要在 npm ci 之前设置 NODE_ENV=production。这样做会导致 npm 跳过 devDependencies,而 Strapi 需要它们来编译管理面板。构建可能看似成功,但会产生损坏或不完整的 admin 包。

# Build stage
FROM node:22-alpine AS build
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev bash vips-dev git > /dev/null 2>&1

WORKDIR /opt/
COPY package.json package-lock.json ./
RUN npm install -g node-gyp
RUN npm config set fetch-retry-maxtimeout 600000 -g && npm ci
ENV PATH=/opt/node_modules/.bin:$PATH

WORKDIR /opt/app
COPY . .
# Uncomment the following lines to pin the admin panel URL at build time.
# Without them the admin panel calls the origin it is served from, which is
# what you want behind a proxy; pin it only when the admin is served from a
# different origin than the API.
# ARG STRAPI_ADMIN_BACKEND_URL
# ENV STRAPI_ADMIN_BACKEND_URL=${STRAPI_ADMIN_BACKEND_URL}
ENV NODE_ENV=production
RUN npm run build

# Production stage
FROM node:22-alpine
RUN apk add --no-cache vips-dev
ENV NODE_ENV=production

WORKDIR /opt/
COPY --from=build /opt/package.json /opt/package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force  # --omit=dev replaces the deprecated --only=production
ENV PATH=/opt/node_modules/.bin:$PATH

WORKDIR /opt/app
COPY --from=build /opt/app ./

RUN chown -R node:node /opt/app
USER node
EXPOSE 1337
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
  CMD wget --quiet --tries=1 --spider http://localhost:1337/_health || exit 1
CMD ["npm", "run", "start"]
优化镜像体积

COPY --from=build /opt/app ./ 这一行会复制整个应用目录,包括源文件。为了获得更精简的镜像,你可以用选择性复制来替换它(例如 dist/build/、config/、public/、src/)。Strapi 5 的 admin 包位于 dist/build/,因此请确保包含该路径。这是可选的,因为 Strapi 在运行时需要其中大部分文件。

与开发 Dockerfile 的主要区别

构建阶段会安装所有依赖(包括 devDependencies),因为 npm run build 步骤需要它们来编译管理面板。生产阶段随后仅安装生产依赖,使最终镜像保持精简。

为生产环境添加 Docker Compose

以下 docker-compose.prod.yml 适用于生产部署。它使用 PostgreSQL 并包含健康检查。Strapi 端口绑定到 127.0.0.1,以便只有本地反向代理能够访问它。

services:
  strapi:
    container_name: strapi
    build:
      context: .
      dockerfile: Dockerfile.prod
    image: strapi:latest
    restart: always
    env_file: .env
    environment:
      NODE_ENV: production  # Overrides the development value from .env
    ports:
      - "127.0.0.1:1337:1337"
    networks:
      - strapi
    depends_on:
      strapiDB:
        condition: service_healthy

  strapiDB:
    container_name: strapiDB
    restart: always
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ${DATABASE_USERNAME}
      POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
      POSTGRES_DB: ${DATABASE_NAME}
    volumes:
      - strapi-data:/var/lib/postgresql/data/
    networks:
      - strapi
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DATABASE_USERNAME} -d ${DATABASE_NAME}"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  strapi-data:

networks:
  strapi:
    name: strapi
    driver: bridge
  •  不要将数据库端口暴露给主机。strapiDB 服务上面没有 ports 映射,使其只能被 strapi 网络上的其他容器访问。
  •  持久化上传文件。生产环境的 docker-compose 没有为 /opt/app/public/uploads 挂载卷。如果你使用默认的本地上传提供方,请添加一个命名卷(例如 strapi-uploads:/opt/app/public/uploads),或者配置一个外部提供方,例如 AWS S3 或 Cloudinary。
  •  保护你的密钥。生产环境的 docker-compose 使用 env_file: .env,这对于单主机部署是可以接受的。对于编排环境,建议使用 Docker secrets、由你的编排器(Kubernetes、ECS)注入的环境变量,或专用的密钥管理器(HashiCorp Vault、AWS Secrets Manager)。

构建并发布

要构建生产 Docker 镜像,运行以下命令:

docker build \
  -t mystrapiapp:latest \
  -f Dockerfile.prod .

构建完成后,你可以将镜像发布到 Docker 镜像仓库。出于生产用途,请使用私有镜像仓库,因为你的 Docker 镜像可能包含敏感配置。

常用的容器镜像仓库包括:

社区工具和镜像

Strapi 不提供官方 Docker 镜像(请参阅 FAQ)。以下社区维护的工具和镜像可以帮助你入门。

如果你想将此列表中的工具添加进来,请在 Strapi 文档仓库 上发起一个拉取请求。

@strapi-community/dockerize CLI

@strapi-community/dockerize 包是一个 CLI 工具,可为 Strapi 项目生成 Dockerfile 和 docker-compose.yml 文件。

要开始使用,请在现有的 Strapi 项目文件夹中运行 npx @strapi-community/dockerize@latest 并遵循 CLI 提示。

更多信息,请参阅官方的 GitHub 仓库 或 npm 包。

社区维护的 Docker 镜像

社区成员维护的预构建 Docker 镜像可用于 Strapi 5。这些镜像让你无需编写自己的 Dockerfile 即可运行 Strapi。

WARNING

这些镜像由社区维护,不受 Strapi 官方支持。在生产环境中使用它们之前,请审查其文档和源代码。

  • naskio/strapi:积极维护的镜像,跟踪 Strapi 5 的发布版本。可在 Docker Hub 上获取。
  • vshadbolt/strapi:同时支持 AMD64 和 ARM64 架构。跟踪 Strapi 5 的发布版本。

故障排查

以下章节详述了在 Apple Silicon 机器上与 Sharp 和 ARM 构建相关的一些常见问题。

INFO

本页面上的 Dockerfile 在 Alpine 包列表中不包含 nasm。它曾包含在此指南的旧版本中,但对于 Sharp 或 libvips 的编译并非必需。

Sharp 和 libvips 错误

Sharp 是 Strapi 使用的图像处理库。它依赖 libvips,而后者在基于 Alpine 的镜像上需要原生编译。常见的错误消息包括 Cannot find module 'sharp' 或 Error: sharp: Installation error。

要解决 Sharp 问题:

  1. 运行 docker exec <container> node -e "require('sharp')"。如果它因缺少库而报错,说明运行时阶段缺少上述 Alpine 包。如果它因 glibc/musl 不匹配而报错,请切换到 node:22-slim(参见步骤 2)。

  2. 验证你的 Dockerfile 是否安装了所需的 Alpine 包:

    RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev bash vips-dev git
    
  3. 如果问题仍然存在,请切换到 node:22-slim(基于 Debian)以避免原生库的兼容性问题 Alpine Linux 使用一个名为 musl 的轻量级 C 库,而非 Debian 和 Ubuntu 所使用的标准 glibc。一些 npm 包附带为 glibc 预编译的二进制文件,无法在 Alpine 上工作。切换到像 node:22-slim 这样基于 Debian 的镜像可以完全避免此问题。:

    FROM node:22-slim
    RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
    
  4. 对于 ARM 构建(请参阅下文),在安装依赖之前添加以下环境变量:

    ENV SHARP_IGNORE_GLOBAL_LIBVIPS=1
    

Apple Silicon 和 ARM 构建

docker-compose 文件中的 platform: linux/amd64 标志会在基于 ARM 的机器(Apple M1/M2/M3)上强制容器在 x86 仿真下运行。这可以工作,但比原生 ARM 构建要慢。

要实现原生 ARM 性能:

  • 从你的 docker-compose 文件中移除 platform: linux/amd64 行。
  • 使用 node:22-alpine 或 node:22-slim 作为基础镜像。两者都原生支持 ARM64。
  • 确保 Sharp 依赖已正确安装(请参阅 Sharp 和 libvips 错误)。
NOTE

platform: linux/amd64 标志在上述 docker-compose 示例 中已被注释掉。如果你的数据库镜像没有提供 ARM64 变体,请取消注释它。

数据库连接问题

DANGER

Strapi 应用必须使用由 Strapi 应用创建的数据库。连接到预先存在的非 Strapi 数据库,或连接到 Strapi v3 数据库,都不受支持,并可能导致数据丢失,例如表被删除。

如果 Strapi 在 Docker 中无法连接到数据库,请检查以下各项:

  1. 验证 DATABASE_HOST 是否与你的 docker-compose 文件中的服务名称(例如 strapiDB)匹配,而不是 localhost 或 127.0.0.1。容器通过 Docker 网络使用服务名称进行通信。

  2. 检查是否与本地数据库实例存在端口冲突。如果同一端口上已经在本地运行了一个数据库:

    • 停止本地数据库,
    • 或者更改 docker-compose 文件中的主机侧端口映射(例如 "5433:5432")。
  3. 在你的数据库配置中将连接池的 min 值设置为 0,因为 Docker 可能会杀死空闲连接:

    JavaScript

    module.exports = ({ env }) => ({
      connection: {
        client: env('DATABASE_CLIENT'),
        // ...
        pool: {
          min: 0,
          max: 10,
        },
      },
    });
    

    TypeScript

    export default ({ env }) => ({
      connection: {
        client: env('DATABASE_CLIENT'),
        // ...
        pool: {
          min: 0,
          max: 10,
        },
      },
    });
    
  4. 如果数据库容器在空闲一段时间后变得无法访问,请在你的数据库配置中向池配置添加超时设置:

    pool: {
      min: 0,
      max: 10,
      acquireTimeoutMillis: 60000,
      idleTimeoutMillis: 30000,
    },
    

接下来做什么?

既然 Strapi 已经在 Docker 容器中运行,你可以:

设置管理面板并使用内容类型构建器创建你的第一个内容类型。

配置反向代理并部署到生产环境(请参阅部署文档)。

探索环境配置以微调你的 Strapi 实例。