Skip to content

Conversation

@aSeriousDeveloper
Copy link
Contributor

Laravel Octane

Laravel Octane has its own Caddyfile. It uses various variables that are populated during the startup command.

Typically, this means you need to perform your own overrides to this image's FrankenPHP config to make the two work together. Fortunately, this is pretty simple using the following steps:

  1. Updating your environment to use this: (thanks Discord User coolio85!)
CADDY_PHP_SERVER_OPTIONS: |
  index frankenphp-worker.php
  try_files {path} frankenphp-worker.php
  # Required for the public/storage/ directory...
  resolve_root_symlink
FRANKENPHP_CONFIG: |
  worker {
    file "/var/www/html/public/frankenphp-worker.php"
  }
  1. Updating your docker run command to use octane & this config file:
CMD ["php", "artisan", "octane:frankenphp", "--caddyfile=/etc/frankenphp/Caddyfile"]

The Problem

This all works pretty well, up until you want to enable worker mode or specify the number of workers. This is because we're overriding the Caddyfile config with environment variables. This is acceptable within the file itself, but if we attempt to do the same thing within our FRANKENPHP_CONFIG environment variable, we get the folowing:

INFO  Error: adapting config using caddyfile: Unexpected '{}' at end of line, at /etc/frankenphp/Caddyfile:20.

The Fix

To fix this issue, we need to directly include the environment variables within the file. Therefore, this PR does exactly that. This PR adds 2 environment variables that can be overridden to the frankenphp config:

  • CADDY_SERVER_WORKER_DIRECTIVE
  • CADDY_SERVER_WATCH_DIRECTIVES

If they're empty, they should be safely ignored. However, this'll mean they can be populated separately, either automatically by Octane, or manually by the user.

Additionally, if the worker or num directives are already specified elsewhere, it still works! The watch directive will simply append to the watchlist, while num I believe cascades to the last specified value.

Extra Stuff

The Octane config does specify a few other environment variables:

  • CADDY_SERVER_EXTRA_DIRECTIVES
  • CADDY_SERVER_ADMIN_HOST
  • CADDY_SERVER_ADMIN_PORT
  • etc etc I don't wanna list them all out

But these may be more difficult to add in, either because they've already been specified in this Caddyfile (eg CADDY_SERVER_EXTRA_DIRECTIVES), or because they require overriding / changes other parts of the Caddyfile if we wanted to make them work.

Specify the `CADDY_SERVER_WORKER_DIRECTIVE` and `CADDY_SERVER_WATCH_DIRECTIVES` directives that are populated by Laravel Octane
@jaydrogers
Copy link
Member

Thanks a ton for chiming in on this @aSeriousDeveloper! I always appreciate reviewing your PRs.

I am a novice with Laravel Octane, so I am still learning the ropes what's the best user experience. To my understanding, there are two Caddyfiles being presented:

Classic Mode: /etc/frankenphp/Caddyfile (provided by ServerSideUp)

  • This file is highly optimized to run in production with caching, SSL, etc

Worker Mode: Use the official Laravel Caddyfile

  • This is completely controlled by Laravel

My first approach (not saying it's 100% right)

I documented to use the following command on start up:

services:
  php:
    image: serversideup/php:8.4-frankenphp
    ports:
      - "80:8080"
    volumes:
      - .:/var/www/html/
    # Start Octane in worker mode
    command: ["php", "artisan", "octane:start", "--server=frankenphp", "--port=8080"]
    # Set healthcheck to use our native healthcheck script for Octane
    healthcheck:
      test: ["CMD", "healthcheck-octane"]
      start_period: 10s

Note

Notice how I'm NOT specifying a Caddyfile. This will use Laravel's default Caddyfile.

If we suggest to just use Laravel's default Caddyfile

There are pros and cons to this approach:

Pros

  • As Laravel releases new versions of Octane (which may be compatible with only certain versions of Laravel), they can specifically control the Caddyfile that's compatible with that version of Octane
  • When people configure Laravel Octane, we can exactly follow Laravel's official docs

Cons

  • Not all variables from our documentation will be available (specifically the CADDY_* variables), but our PHP_* variables will still work

Next steps

Let me know your thoughts what you think makes sense. I am very interested in having a community discussion on this.

I can easily add extra variables that you propose, but my only hesitation is if Laravel starts changing variables with newer versions of Octane that only are available in Laravel 13+, then this could turn into a lot of maintenance and documentation for people who are running Laravel 12 or lower.

Thoughts?

@aSeriousDeveloper
Copy link
Contributor Author

aSeriousDeveloper commented Nov 12, 2025

@jaydrogers Yeah good point on wanting it to be more easily configurable & not have compatibility problems. However, I do still want some of the improved Caddyfile features...

Not entirely sure what the best-fit solution would be in this case, some spitball suggestions though:

Injecting as much as possible using Octanes exposed environment variables

Namely, the CADDY_EXTRA_CONFIG doesn't seem to get overridden within the Octane startup, so you should be able to use that. It's within the global scope of the Caddyfile so you should be able to perform both global & server based configs with it?

Then again, loading a bunch of config via an environment variable (even if you just do it via import) feels wonky.

Advising to change the config to the one provided here

This means they get the specialised config provided, and if we include the extra env variables, then they get the Octane config too. However, it runs into the problem you mentioned of future compatibility, so ehhh.

Providing a secret, third config

Not sure on this one, but either provide the config as an additional one alongside the existing one within the image (feels bloated), or through something like Spin Pro means you can have it there for those who want it. Namely, if it were part of Spin Pro or something like that, you can have it automatically included within the Dockerfile / Docker Compose. It'd effectively be the same config included here but with the added environment variables as much as possible, as well as maybe cut back on what becomes redundant? Such as not needing the SSL mode configs anymore as it'll always be behind a reverse proxy.

@jaydrogers
Copy link
Member

I love this discussion! We're definitely on the same page.

Maybe we start here when you said:

However, I do still want some of the improved Caddyfile features...

What features from the Server Side Up Caddyfile do you feel are missing in the default Octane configuration (specifically in the use case of running Octane).

I know we provide a lot of caching and security stuff, but is all our stuff compatible with Octane (or even worker mode in general)? 😅

@aSeriousDeveloper
Copy link
Contributor Author

I think it's mostly because I don't wanna lose anything that was previously added, but the big ones that stand out are:

  • Cloudflare Proxying (very useful imo)
  • The healthcheck (although that might have support in Octane already)
  • Asset Caching
  • Filetype rejections & Header removal (although these can be handled by Cloudflare anyway so eh)

Honestly overall though, I think it's mostly just peace of mind, outside of the Cloudflare proxying which I do very much like the concept of. Then again, isn't this also supported by a Caddy Module?

@jaydrogers
Copy link
Member

You're right...

We've gotta bring these variables in. I don't want to lose that either 🤣

I am in the middle of a project today, but I will have more thought on this tomorrow. Feel free to add more notes to this PR if you have additional thoughts too.

@kohenkatz
Copy link
Contributor

Honestly overall though, I think it's mostly just peace of mind, outside of the Cloudflare proxying which I do very much like the concept of. Then again, isn't this also supported by a Caddy Module?

The big thing for me is also the Cloudflare proxy trust (along with also trusting our Docker network and own AWS EC2 IPv6 range).

Right now I use a custom Caddyfile and a custom Caddy build with Octane.

This uses the Cloudlfare IPs module linked above, along with the combine module. I don't think Cloudflare changes their IP ranges very often anymore, so it's probably fine to keep using a static list instead of the module, but not having to provide my own Caddyfile would be nice.

My thought is that Octane doesn't really need much to be changed in the Caddyfile - just a few lines that reference frankenphp-worker.php. Maybe an import php-app-octane that can be enabled with an environment variable, similar to the existing way of enabling/disabling features like SSL mode?


Here are the relevant snippets of what I have done until now:

Dockerfile

FROM dunglas/frankenphp:1.9.1-builder-php8.4.13-trixie AS builder

COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy

RUN CGO_ENABLED=1 \
    XCADDY_SETCAP=1 \
    XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
    CGO_CFLAGS=$(php-config --includes) \
    CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
    xcaddy build \
        --output /usr/local/bin/frankenphp \
        # Default modules (except vulcain)
        --with github.com/dunglas/frankenphp=./ \
        --with github.com/dunglas/frankenphp/caddy=./caddy/ \
        --with github.com/dunglas/caddy-cbrotli \
        --with github.com/dunglas/mercure/caddy \
        # Our extra Caddy modules here
        --with github.com/fvbommel/caddy-combine-ip-ranges \
        --with github.com/fvbommel/caddy-dns-ip-range \
        --with github.com/WeidiDeng/caddy-cloudflare-ip

FROM dunglas/frankenphp:1.9.1-php8.4.13-trixie AS runner

# [SNIP - app-specific stuff]

COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp

Caddyfile

This is the only part that is added to the original Octane Caddyfile, at the end of the global options section.

	servers {
		# Docker/AWS Local IPv4, Docker IPv6, AWS IPv6, and the rest are Cloudflare (updated 2024-07-31)
		trusted_proxies combine {
			# Docker and AWS local IPv4 and IPv6
			static 172.16.0.0/12 fd00::/8 2600:f18:xxxx:xxxx::/56
			cloudflare {
				interval 12h
				timeout 15s
			}
		}
		trusted_proxies_strict
	}

@aSeriousDeveloper
Copy link
Contributor Author

My thought is that Octane doesn't really need much to be changed in the Caddyfile - just a few lines that reference frankenphp-worker.php. Maybe an import php-app-octane that can be enabled with an environment variable, similar to the existing way of enabling/disabling features like SSL mode?

Yeah this is largely true. The core pieces of Octane are essentially just loading the worker. However the additional crux for me comes from the specific variables it also references for building its config.

(Also, another feature I neglected to directly mention, the support it includes for Mercure via the CADDY_SERVER_EXTRA_DIRECTIVES variable).

Having an imported config would probably solve some of the issues, but I was thinking:

Considering this image & the caddyfile are still within beta, what would be solved and what problems would be caused if we more directly emulated Octane's config? Looking at it, probably wouldn't solve much and would be pretty annoying to do, but I thought I'd mention at least.

But looking again, specific areas that clash are:

There's other bits that are different between the files but they can be more easily reconciled through just setting environment variables.

Considering all that, if we did wanna make these two work together, it probably would require either rejigging our own Caddyfile heavily to have it mesh properly (including renaming our environment variables to clash less), or to have a dedicated Caddyfile for Octane?

Alternatively, maybe it's possible to place a PR into Laravel to change their Caddyfile variables to be more Octane specific? They use generic Caddy names but unless these variables are merged somehow that just causes whatever you've set to get overridden.

@jaydrogers
Copy link
Member

Man, I am really torn on this. I think for the v4.0 release (which I am hoping to move to stable this week 🤞), we'll just stick with our documented process on using the official Laravel Octane Caddyfile:

services:
  php:
    image: serversideup/php:8.4-frankenphp
    ports:
      - "80:8080"
    volumes:
      - .:/var/www/html/
    # Start Octane in worker mode
    command: ["php", "artisan", "octane:start", "--server=frankenphp", "--port=8080"]
    # Set healthcheck to use our native healthcheck script for Octane
    healthcheck:
      test: ["CMD", "healthcheck-octane"]
      start_period: 10s

Octane enhancements in the future

Once v4.0 is out, then we can either:

  1. Optimize our /etc/frankenphp/Caddyfile to have better support for Laravel Octane & Classic Mode
  2. Make a /etc/frankenphp/octane.caddyfile that has cherry picked features specifically for Octane, but also has the enhancements of CloudFlare, etc

Octane is very important so this would likely be a v4.1 or v4.2 thing.

Thoughts?

@kohenkatz
Copy link
Contributor

@jaydrogers I agree, I think it makes sense to go with what's there now for 4.0 and improve in anther release. (As one of my teachers used to say, "don't let the perfect be the enemy of the good".)

Regarding your two suggested options: theoretically it would be nice to have a single unified base configuration with as few differences as possible (i.e. your first option). However, as @aSeriousDeveloper points out, Octane expects certain variables to do certain things, so it's more likely that a separate file which takes those things into account probably makes more sense (i.e. your second option).

@aSeriousDeveloper
Copy link
Contributor Author

I agree with the above.

I think I might include my secret third option of making a PR to Octane to rename their environment variables to something more specific to itself.

That would make it a fair bit easier to integrate with it, but the viability of this depends entirely on the PR actually getting accepted lol.

@jaydrogers jaydrogers changed the base branch from 280-create-a-frankenphp-variation to main November 19, 2025 17:11
@jaydrogers
Copy link
Member

Thanks! I appreciate you both giving me the confidence to launch v4.0.0. Shortly after, we'll have PHP 8.5 support.

In regards to improving Octane support, I am very interested in making sure we're offering a "batteries included" experience with Octane. So please let me know if you have any ideas on this discussion.

I feel as we continue to build and learn with these new technologies, the solution will basically present itself of what we'll need to do. I'll be sure to implement something following the principles of something that "just works" out of the box 😃

Please keep me posted as you learn more. I am beyond grateful to have both of your contributions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants