Symfony running in Cloudflare

I use both Amazon AWS and Cloudflare, primarily all my compute is on Amazon and I just use Cloudflare for DNS and tunnelling to my K3s cluster at home.

Cloudflare has been growing it's compute infrastructer over the last few years and I'm curious as to what it can offer me as a PHP / Symfony developer.

Here's my simple guide to getting a POC running on Cloudflare.

Prerequisites: You pay $5 a month for Cloudflare Workers.

npm i -D wrangler@latest

npm create cloudflare@latest -- --template=cloudflare/templates/containers-template

It will ask you to name your project, I went with 'symfony-worker-poc'.

I chose to deploy my app, which does so immediately, but I guess you can do this later if you want.

cd symfony-worker-poc

(I've assumed you already have symfony cli installed, if not do that first) symfony new symfonyapp --version="7.3.x" --webapp

Now edit your Dockerfile:

# ------------------------------------------
# Stage 1: build vendor/ with Composer only
# ------------------------------------------
FROM composer:latest AS vendor
WORKDIR /app

# Copy only dependency manifests first for layer caching
COPY symfonyapp/composer.json symfonyapp/composer.lock* /app/

# Install prod deps (skip scripts; bin/console not present here)
# BuildKit cache (optional) speeds rebuilds if you enable DOCKER_BUILDKIT=1
RUN --mount=type=cache,target=/tmp/composer-cache \
    composer install \
      --no-dev --no-interaction --prefer-dist --no-progress --no-scripts \
      --no-ansi \
      --working-dir=/app \
      --no-cache

# (No need to copy the whole app in this stage; we'll do it in final)

# ------------------------------------------
# Stage 2: final runtime (FrankenPHP)
# ------------------------------------------
FROM dunglas/frankenphp:1.9.1-php8.3

# Keep your extensions
RUN install-php-extensions intl opcache

ENV APP_ENV=prod \
    PHP_OPCACHE_ENABLE=1 \
    PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \
    SERVER_NAME=":8080"

WORKDIR /app

# Bring only vendor from builder
COPY --from=vendor /app/vendor /app/vendor
COPY --from=vendor /app/composer.json /app/composer.json
COPY --from=vendor /app/composer.lock /app/composer.lock

# Copy only what's needed at runtime (avoid tests, docs, node_modules, etc.)
# Adjust if your app uses additional dirs (e.g., translations/)
COPY symfonyapp/bin/        /app/bin/
COPY symfonyapp/config/     /app/config/
COPY symfonyapp/public/     /app/public/
COPY symfonyapp/src/        /app/src/
COPY symfonyapp/templates/  /app/templates/
COPY symfonyapp/.env*       /app/
# COPY symfonyapp/translations/ /app/translations/

# Minimal Caddyfile for FrankenPHP
RUN printf ':8080 {\n  root * /app/public\n  encode zstd gzip\n  php_server\n  try_files {path} /index.php\n}\n' > /etc/caddy/CaddyFILE

# Warm cache now that bin/console exists (safe to ignore failures for POC)
RUN php bin/console cache:clear --env=prod || true

CMD ["frankenphp", "run", "--config", "/etc/caddy/CaddyFILE"]

After that you should be able to run: npx wrangler deploy

At the end you'll get a URL you can hit, something like: https://symfony-worker-poc.madelin.workers.dev/

Just be aware you don't yet have a controller, so it will show the Symfony error page.

I've noticed the cold start is quite long, ~1 second ish but a bigger app will be longer.

You have some control over this, it's the sleepAfter value in the index.ts file. It can be “0s” so it doesn't sleep however, that could get expensive. Other acceptable values are: “30s”, “60s” and “120s”.

You could also ping it periodically to keep it alive.

In the next installment we'll look at how we handle classic SaaS stuff like uploads, databases and all that jazz. Keeping it as Cloudflare native as possible.