Cloudflare Containers怎么运行PHP程序?从Dockerfile到Workers路由完整教程

Cloudflare Workers不能直接运行PHP,但可以通过Containers部署标准PHP镜像。本文提供可复制的Dockerfile、Worker路由和Wrangler配置,并讲清部署验证、冷启动、持久化、Secrets、费用与常见错误。

先说结论:Cloudflare Workers 本身不能直接解释执行 PHP,但 Cloudflare Containers 可以运行标准 OCI/Docker 镜像。正确架构是“Worker 接收公网请求并选择容器实例,容器内部运行 PHP Web 服务器”。下面用一个可以直接部署的最小项目,把 Dockerfile、Worker 路由、Wrangler 配置、部署验证、费用和持久化边界一次讲清。

本文配置于 2026 年 10 月 11 日按 Cloudflare 官方文档核对。Cloudflare Containers 仍在快速迭代,复制旧教程前先看配置字段是否已经变化。

什么时候应该把 PHP 放进 Cloudflare Containers

适合的场景是:现有 PHP 应用不方便改写为 Worker、需要 PHP 扩展或系统二进制、需要运行 Laravel/Symfony 的后台任务,或者希望利用 Cloudflare 的入口网络,把动态应用按需启动。它不是“把一个 index.php 上传到 Workers”——Worker 和 PHP 是两个运行层。

组成 职责 本文示例
Cloudflare Worker 接收请求、鉴权、改写路径、选择容器实例 src/index.ts
Container 运行 Linux 镜像与 PHP Web 服务 php:8.4-apache
Durable Object binding 给容器实例提供可寻址身份与生命周期管理 PHP_CONTAINER

Cloudflare 的官方入门文档明确说明:镜像需要支持 linux/amd64;Worker 通过 Durable Object binding 调用容器;首次部署后,容器资源可能还要等待几分钟才能完成配置。

准备条件

  • Cloudflare 账户,并开通 Workers Paid 计划。Containers 的计算用量包含在每月 5 美元的 Workers Paid 计划中,超出后按资源用量收费;具体额度见文末费用说明。
  • 本机安装 Node.js、npm 和 Docker,且 docker info 能正常返回。
  • 已经登录 Wrangler:npx wrangler login。
  • 准备一个用于测试的 Cloudflare Workers 子域名;绑定自定义域名可以部署成功后再做。

如果你还在规划服务器、域名、CDN 与应用部署方式,可以先查看 SHOPAGG 的独立站服务页面,把运行环境和业务需求定清楚,再决定是否需要把传统 PHP 应用迁到容器。

第一步:创建 Containers 项目

先从 Cloudflare 官方模板创建项目:

npm create cloudflare@latest -- --template=cloudflare/templates/containers-template
cd <你的项目目录>
docker info

模板已经安装 Wrangler 和 @cloudflare/containers。接下来把示例 Go 服务替换为 PHP。项目保留下面四个关键文件:

cloudflare-php/
├── Dockerfile
├── public/
│   └── index.php
├── src/
│   └── index.ts
└── wrangler.jsonc

第二步:编写 PHP 程序

新建 public/index.php。这个页面返回 JSON,便于确认 PHP 版本、容器位置和实例身份:

<?php
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');

echo json_encode([
    'ok' => true,
    'message' => 'PHP is running in Cloudflare Containers',
    'php_version' => PHP_VERSION,
    'region' => getenv('CLOUDFLARE_REGION') ?: null,
    'location' => getenv('CLOUDFLARE_LOCATION') ?: null,
    'container_id' => getenv('CLOUDFLARE_DURABLE_OBJECT_ID') ?: null,
    'request_time' => gmdate('c'),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);

CLOUDFLARE_REGION、CLOUDFLARE_LOCATION 和 CLOUDFLARE_DURABLE_OBJECT_ID 是平台自动注入的运行时变量,字段来源可在环境变量文档核对。

第三步:制作 PHP 镜像

在项目根目录创建 Dockerfile:

FROM php:8.4-apache

RUN sed -ri 's!Listen 80!Listen 8080!g' /etc/apache2/ports.conf \
 && sed -ri 's!<VirtualHost \*:80>!<VirtualHost *:8080>!g' \
    /etc/apache2/sites-available/000-default.conf \
 && echo 'ServerName localhost' >> /etc/apache2/apache2.conf

COPY public/ /var/www/html/

EXPOSE 8080

这里选择 PHP 8.4 的 Apache 官方镜像,并把 Apache 从 80 端口改到 8080。原因不是 PHP 只能监听 8080,而是后面的 Container 类会把 defaultPort 设为 8080;两个值必须一致,否则 Worker 会一直等待一个并不存在的监听端口。

正式项目不要使用 latest 标签。应固定到经过测试的 PHP 主版本甚至补丁版本,并按PHP 支持周期安排升级。需要 pdo_mysql、intl 或 gd 等扩展时,在 Dockerfile 中显式安装,避免“本地能跑、线上缺扩展”。

第四步:让 Worker 把请求送进 PHP 容器

将 src/index.ts 改为:

import { Container } from "@cloudflare/containers";

export class PhpContainer extends Container {
  defaultPort = 8080;
  sleepAfter = "10m";
  envVars = {
    APP_ENV: "production",
  };

  override onStart() {
    console.log("PHP container started");
  }

  override onError(error: unknown) {
    console.error("PHP container error", error);
  }
}

interface Env {
  PHP_CONTAINER: DurableObjectNamespace<PhpContainer>;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    if (!url.pathname.startsWith("/php")) {
      return new Response("Open /php", { status: 404 });
    }

    // 单应用示例固定使用同一个实例名。
    const container = env.PHP_CONTAINER.getByName("php-app");

    // 去掉公开入口 /php,避免 Apache 把它当作真实目录。
    url.pathname = url.pathname.replace(/^\/php/, "") || "/";
    const upstream = new Request(url.toString(), request);
    return container.fetch(upstream);
  },
} satisfies ExportedHandler<Env>;

sleepAfter = "10m" 表示实例闲置 10 分钟后可以休眠。后续请求会重新启动它,因此低频应用可以缩到零,但用户可能遇到冷启动。Cloudflare 的FAQ称冷启动通常在 1—3 秒区间,实际时间仍取决于镜像体积和启动逻辑。

第五步:配置 wrangler.jsonc

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "php-on-cloudflare-containers",
  "main": "src/index.ts",
  "compatibility_date": "2026-10-01",
  "containers": [
    {
      "class_name": "PhpContainer",
      "image": "./Dockerfile",
      "instance_type": "lite",
      "max_instances": 1
    }
  ],
  "durable_objects": {
    "bindings": [
      {
        "name": "PHP_CONTAINER",
        "class_name": "PhpContainer"
      }
    ]
  },
  "exports": {
    "PhpContainer": {
      "type": "durable-object",
      "storage": "sqlite"
    }
  },
  "observability": {
    "enabled": true
  }
}

class_name 必须与 TypeScript 导出的类同名,binding 名称必须与 env.PHP_CONTAINER 一致。max_instances 是允许同时运行的最大实例数,不是预热数量。本文只有一个固定实例名,因此先设为 1 最容易排查。

lite 当前提供 1/16 vCPU、256 MiB 内存和 2 GB 磁盘,适合这个最小示例。Laravel、图片处理或 Composer 依赖较多的项目,通常应从 basic 起测;完整规格见实例类型与限制。

第六步:本地测试并部署

npx wrangler dev

另开一个终端请求:

curl -i http://localhost:8787/php

看到 HTTP 200,并且 JSON 中有 "ok": true 与 PHP 版本,说明 Worker 到容器的链路正常。随后部署:

npx wrangler deploy
npx wrangler containers list
npx wrangler containers images list

第一次执行 wrangler deploy 会调用本机 Docker 构建镜像、上传镜像并部署 Worker。Cloudflare 提醒首次配置可能需要几分钟;Worker URL 先返回,不代表容器已经可用。不要看到一次 502 就连续改十遍代码,先检查容器状态和日志。

如何验证部署确实成功

  1. 访问 https://你的-worker.workers.dev/php,确认 HTTP 200。
  2. 响应中的 php_version 应与镜像版本一致。
  3. region、location 或 container_id 至少应出现平台注入的信息。
  4. Cloudflare 控制台进入 Workers & Pages → Containers,查看 Status、Metrics 和 Logs。
  5. 等待超过 sleepAfter 后再次访问,观察冷启动时间是否符合业务接受范围。

数据库、上传文件和 Session 怎么处理

这是把 PHP 放入 Containers 最容易踩的坑:容器磁盘默认是临时的。实例休眠后,下次启动会获得从镜像恢复的全新文件系统。把 SQLite 数据库、用户上传、订单附件或 PHP Session 只写到本地目录,迟早会丢。

  • 用户上传:放到 R2 或其他对象存储,不要依赖容器本地目录。
  • 业务数据库:使用可从容器网络访问的托管 MySQL/PostgreSQL;凭据通过 Worker Secret 或 Secrets Store 注入。
  • Session:使用 Redis、数据库或无状态签名 Cookie。
  • 临时缓存:可以写本地盘,但必须允许随时丢失并重建。

Cloudflare 支持快照和 R2 FUSE,但快照是不可变的时间点状态,FUSE 也不应期待本地 SSD 的性能。官方对临时磁盘、快照和 FUSE 的边界说明见持久化 FAQ。

环境变量与密钥不要写进镜像

普通配置可以放在 Container 类的 envVars,数据库密码、API Key 等敏感值应使用 Worker Secrets 或 Secrets Store。不要在 Dockerfile 中写 ENV DB_PASSWORD=...,因为镜像层和构建记录可能泄露它。Cloudflare 提供了环境变量与 Secrets 示例,部署生产应用前应按该模式改造。

费用怎么算,免费计划能不能用

截至 2026 年 10 月 11 日,Containers 计费包含在每月 5 美元的 Workers Paid 计划中,免费计划不含容器资源。官方价格页列出的月度包含量为 25 GiB-hours 内存、375 vCPU-minutes CPU 和 200 GB-hours 磁盘;超出后按 10ms 粒度及对应资源单价计费。容器休眠后停止计算容器资源费用,但请求还会涉及 Workers、Durable Objects、日志和网络出站计费。价格会变动,投产前以Cloudflare Containers Pricing为准。

常见错误与排查顺序

1. Cannot connect to the Docker daemon

本机 Docker 没启动。先让 docker info 成功,再执行 wrangler deploy。

2. Worker 正常,但访问 /php 返回 502 或超时

先确认 Dockerfile 中 Apache 的监听端口与 defaultPort 都是 8080;再等首次容器配置完成,并查看 Containers 日志。端口不一致是最常见原因。

3. 镜像在 Apple Silicon 本机可运行,部署后启动失败

Cloudflare 当前要求 linux/amd64。构建含原生二进制的自定义镜像时,确保依赖和入口程序支持该架构,必要时先执行 docker build --platform linux/amd64 . 验证。

4. 发布新版本后仍像旧代码

先用 npx wrangler containers images list 确认新镜像已上传,再看 rollout 和实例日志。Cloudflare 部署时 Worker 先上线,容器镜像默认渐进更新,不保证两者在同一瞬间全部切换。

5. PHP 上传的文件重启后消失

这不是权限故障,而是临时磁盘的设计结果。把永久数据迁移到对象存储或外部数据库,本地目录只放可重建缓存。

上线前检查清单

  • 镜像固定 PHP 版本,支持 linux/amd64,并完成漏洞扫描。
  • 容器监听端口与 defaultPort 一致。
  • 密钥使用 Secrets 注入,不进入代码仓库和镜像层。
  • 数据库、上传与 Session 不依赖临时磁盘。
  • 测试冷启动、并发、超时、内存上限和回滚流程。
  • 开启日志,但避免把支付信息、Cookie 和用户隐私写入日志。

这个最小项目跑通后,再把 public/ 换成真实 PHP 应用,并逐项补齐扩展、Composer 依赖、外部数据库、对象存储和健康检查。Cloudflare Containers 解决的是“PHP 运行环境能否靠近 Cloudflare 网络按需启动”,应用的数据一致性、安全和容量规划仍然要由你负责。

作者:庄朋龙,SHOPAGG 官方博客编辑,长期关注独立站建站、服务器运维与跨境电商技术基础设施。

分享您的喜爱

发表回复