Kai Ole Hartwig — Blog
9 min read
By

From container to signed application — why I ship TYPO3 as an OCI artifact separated from the runtime

Many TYPO3 projects today are built as complete container images, in which each application brings along its own runtime, PHP configuration and dependencies again. I take a different path and separate runtime and application consistently: I ship the runtime as a hardened golden image and the actual TYPO3 application as a signed OCI artifact. The result is smaller deployments, reproducible releases, faster rollbacks and a markedly clearer supply chain. This article describes the approach, why it fits TYPO3 in particular — and where its limits lie.

TL;DR

The common pattern builds a separate container image per TYPO3 project that packs in the complete runtime. With multiple installations, the largest part of these images is identical (PHP, FrankenPHP, extensions, OS packages, TYPO3 core); only the application itself differs — sitepackage, configuration, extensions, assets. I separate the two: a hardened, centrally maintained runtime as a golden image and the application as a standalone, fully pre-built and signed OCI artifact. At deployment the two meet, the artifact is verified and provided, FrankenPHP starts. Only fileadmin/, var/ and the upload storage stay writable; the application code remains immutable. This is not a dogma against containers (they remain a good transport shell), but a shift of the deployment unit from “individual image” to “signed application”.

The container question

In many projects a TYPO3 build today looks roughly like this:

 

FROM php:8.5
RUN composer install
COPY . .
# TYPO3 setup
# -> individual image per application

 

It works, and over the years it has become the standard way. The price for it only becomes apparent once you run more than one installation: each application brings along its complete runtime again, even though that environment is almost identical across projects.

The real problem

Consider two TYPO3 installations, customer A and customer B. Both use PHP 8.5, FrankenPHP, the same PHP extensions, the same operating-system packages and the same TYPO3 core. Yet two complete images are built, and the largest part of these images is identical. Only a small part differs: the sitepackage, the configuration, the assets and the project-specific extensions.

Put differently: we build, sign, distribute and patch the same foundation again and again, only so that it carries a small, project-specific superstructure. Every security flaw in the runtime forces a rebuild of every single project image. The unit we move is much larger than the unit that actually changes.

Runtime and application are different things

The core of my approach is to separate these two layers deliberately.

Golden image = runtime

The golden image contains everything the application needs to run, but nothing project-specific: FrankenPHP, PHP with native extensions, Caddy, certificates and the security defaults. It is built once centrally, hardened, scanned and signed — and shared by all projects. A flaw in the foundation is patched in exactly one place, not in each project individually.

Application artifact = TYPO3 application

The artifact contains the actual application: TYPO3 core, Composer dependencies including vendor/, the extensions, the sitepackage and the assets. It is what differs from project to project and from release to release — and exactly this small, changing unit is what I version, sign and move.

Why no Composer in production?

A common approach assembles the application only at start time, for example via composer install in an init container. I consider that problematic. A production application should not come into being at the moment of deployment, but should already exist fully built, tested and signed.

composer install at start time means a network access to package sources in the critical path of every pod start, a non-deterministic result (what resolves today may resolve differently tomorrow) and an attack surface exactly where it least belongs — in production. The supply-chain incidents of recent weeks in the npm and PHP ecosystems underline this: anyone who pulls dependencies only at runtime shifts the supply-chain risk into production. Build time is the right place to resolve, verify and pin dependencies.

The application as an OCI artifact and the deployment

I therefore treat the application as a standalone OCI artifact in the registry:

 

registry.example.com
└─ customer-a
   └─ app:v1.4.2
      ├─ vendor/
      ├─ public/
      ├─ config/
      └─ extensions/

 

The artifact already contains all Composer dependencies and is thus fully reproducible. It is built once, signed once and then shipped unchanged. OCI is chosen deliberately here: the same registry, signature and distribution ecosystem I run for images anyway (tags, digests, signatures with cosign, attestation) also carries the application — without it having to be a runnable image.

At deployment two building blocks meet: the golden image (runtime) and the application artifact (application). The startup process is deliberately plain:

 

1. Load artifact         (app:v1.4.2 from the registry)
2. Verify signature      (trust before execution)
3. Provide files         (mount read-only into the runtime)
4. Start FrankenPHP

 

Only the areas TYPO3 really needs at runtime stay writable: fileadmin/, the upload storage and var/. The actual application code remains immutable — it comes from a signed artifact and is no longer modified at runtime.

Benefits

Smaller deployments. Instead of distributing multi-gigabyte images, only the actual application artifact is updated. The shared runtime is already on the nodes anyway.

Faster rollbacks. A rollback is a version switch of the artifact (from app:v1.4.2 back to app:v1.4.1), without rebuilding a container. The runtime stays the same.

Better supply chain. Runtime and application are signed and attested independently. A flaw in the foundation is patched once centrally; a faulty application release is rolled back independently of it. Responsibilities are cleanly separated and individually verifiable.

Higher reproducibility. The application is built once and then shipped unchanged. What ran in staging runs in production — bit for bit the same artifact, not a reassembled approximation.

Does this fit TYPO3?

Yes, and surprisingly well. At runtime TYPO3 needs only a few writable areas, essentially fileadmin/ and var/ (plus the upload storage). The entire application code can remain immutable. TYPO3 thus meets exactly the prerequisite an immutably shipped artifact demands: a clear dividing line between immutable code and mutable state. Anyone already placing persistent assets in object storage via FAL (see the sister post on the RWX volume) has already drawn that separation — the OCI-artifact approach is the consistent continuation at the code level.

Why I am exploring this path — and its limits

I believe that in the long run applications should no longer be viewed as individually built containers. Containers are a good transport shell, but the actual unit of a deployment is the application itself. That is why I am experimenting with an architecture that treats TYPO3 applications as signed OCI artifacts and separates them from their runtime. This is explicitly not the only conceivable path, and I do not sell it as a finished truth — but it leads in a direction I consider right: reproducible, secure and sovereign platforms in which every building block is individually verifiable, individually patchable and individually rollbackable.

The approach has prerequisites that I name openly. It demands discipline in the build: the application must be pre-built completely and deterministically, which requires a clean CI path and pinned dependencies. Runtime versioning becomes its own task, because the golden image and the artifact must stay compatible, so a clear contract definition (PHP version, extension set) between the two is needed. And for very small setups with a single installation the added value is smaller; the approach plays to its strength with multiple platforms sharing a foundation. Anyone who takes that into account gets an architecture that scales better, not worse, with the number of installations under management.

Frequently asked questions

Is an OCI artifact the same as a Docker image?+

No. An OCI artifact uses the same registry and signature ecosystem as images (tags, digests, cosign), but it need not be a runnable image. I use it to transport the finished application — vendor/, public/, config/, extensions — separated from the runtime.

Why not simply composer install in an init container?+

Because that puts a network access and a non-deterministic result into the critical path of every pod start — and shifts the supply-chain risk into production. Dependencies belong resolved, verified and pinned at build time, not at start time.

Which directories must TYPO3 be able to write at runtime?+

Essentially fileadmin/, the upload storage and var/. The application code stays immutable and comes from the signed artifact. Persistent assets belong in object storage via FAL, not on an RWX volume.

How does a rollback work with this approach?+

As a version switch of the artifact, for example from app:v1.4.2 back to app:v1.4.1, without rebuilding a container. The shared runtime stays unchanged.

Is the effort worth it even for a single TYPO3 installation?+

The reproducibility and signing benefit always applies; the efficiency gain from the shared runtime, however, unfolds mainly with multiple platforms sharing the same foundation. For a single setup the added value is smaller.

Conclusion

The reflex to build every TYPO3 project as its own container image stems from a time when image equalled application. If you separate runtime and application (hardened golden image here, signed OCI artifact there), deployments become smaller, rollbacks faster, the supply chain clearer and releases reproducible. TYPO3 is especially suited to this because its mutable state is small and clearly delimited. The question is not “how do I build a good image?”, but “what is actually the unit I ship — and can I sign, verify and roll it back without touching the foundation?”.

Before the next image rebuild hits your whole fleet — let’s talk about your deployment unit.

I build TYPO3 platforms where runtime and application are separated, signed and individually rollbackable.

Architecture review and build-out of your TYPO3 deployment path: a hardened golden image as the shared runtime, the application as a signed OCI artifact, reproducible releases, fast rollbacks and a verifiable supply chain.

Platform operations, not advice on paper — from the CI path through signing to deployment.

Book an appointment directly

About the author

[Translate to English:] Foto von Kai Ole Hartwig.

Kai Ole Hartwig

Freelance DevSecOps consultant · OnlyOle Consulting

Programming since 2002 – self-taught, set up my own business with KO-Web in 2012. Over 100 projects, with a focus on security, performance, automation and quality. Today freelance: DevSecOps consulting, training and software development.

TYPO3, Kubernetes, RWX volume, shared filesystem, NFS, EFS, CephFS, Azure Files, cache, cluster file backend, FAL, object storage, S3, platform operations, DevSecOps, hosting, architecture

TYPO3 on K8s without RWX volume

A shared filesystem is not automatically the best solution for TYPO3 on Kubernetes — often just the one carried over from the single-server era. Separate cache metadata (centralised) from cache files (local per pod, reproducible) and the RWX volume disappears for the cache: less infrastructure, faster pods, one fewer point of failure. Persistent assets like fileadmin belong in object storage via FAL. This is where our Cluster File Backend for TYPO3 came from.

TYPO3, Kubernetes, hosting, high availability, shared filesystem, cloud-native, scaling, performance, platform operations, DevSecOps, architecture, container

5 misconceptions: TYPO3 on Kubernetes

Five assumptions that make TYPO3-on-Kubernetes projects needlessly complex: the mandatory shared filesystem, Kubernetes as automatic high availability, more pods as a performance fix, containers as cloud-native, and Kubernetes for every project. None is a TYPO3 problem — all come from the single-server world. With the rule of thumb for when Kubernetes really pays off.

compressing, npm, Node.js, CVE-2026-40931, CVE-2026-24884, symlink, path traversal, arbitrary file write, git clone, supply chain, CI/CD, lstat, path.resolve, RCE, DevSecOps

compressing CVE-2026-40931 (patch bypass)

An early-June public deep-dive on a patch bypass (CVE-2026-40931) in the npm library compressing: path.resolve()/startsWith validate only the string, not whether a path segment is a symlink on disk. The symlink is not embedded in the archive but planted in advance via git clone; on extraction fs.writeFile follows it and writes outside the target. With mitigation, detection, root cause and operator guidance; fix 2.1.1 / 1.10.5.