Kai Ole Hartwig
7 min read
Low
By

TYPO3 on Kubernetes under IT-Grundschutz, part 8: Making TYPO3 stateless

Kubernetes assumes that any pod can be replaced by another at any time. TYPO3 is not built that way. It keeps a surprising amount of state in its own filesystem, and you only notice once the second pod arrives. This part walks through that state piece by piece and shows where it belongs in a cluster.

Part 8 of 14 of the series "TYPO3 on Kubernetes under IT-Grundschutz". The overview of all parts is in part 1.

01 — What state TYPO3 brings along

A classic TYPO3 installation keeps state in several places that a second pod cannot see:

As long as only one pod runs, this hardly shows. With the second pod the odd errors begin. Users get logged out, images go missing, caches contradict each other. The rule for the cluster is therefore: the pod contains only code and configuration. Everything else lives outside.

02 — Sessions, locks and caches

Sessions and locks belong in a shared key-value store such as Valkey or Redis. For sessions, the TYPO3 core ships a Redis backend. Locks need a strategy that works across pods. The core's file-based strategies only do that with a shared filesystem. The core ships no strategy for Valkey, but it can be extended. A class of your own implements LockingStrategyInterface and is registered in ext_localconf.php via LockFactory::addLockingStrategy(). If it returns a higher priority than the file strategy's 75, the core also uses it for page rendering. Technically, a SET … NX PX with an expiry is enough. That way a crashed pod leaves no lock behind that blocks all the others. What matters is that the store itself is protected: TLS, a separate user per tenant, no connections from outside. What that looks like with mTLS on every hop is covered in part 5.

For caches there are two routes. Frequently used caches such as pages and hash can live in Valkey. Caches that have to live on the filesystem need a backend that copes with several pods. How that works without a ReadWriteMany volume is shown in TYPO3 on Kubernetes without an RWX volume. Either way, a cache flush has to reach every pod, not only the one where it was triggered.

03 — fileadmin and processed images in object storage

Every pod needs to see uploads and _processed_ images. A single ReadWriteOnce volume ties the application to one node. A ReadWriteMany network filesystem is often slow and hard to operate. Object storage such as S3 is the more robust base.

Two routes lead there. TYPO3 can talk to S3 directly through a FAL driver. Or a CSI driver mounts a bucket into the pod as a filesystem, and TYPO3 notices nothing. The second route is transparent but adds latency: every file access becomes a request to the storage. A cache in front of it is therefore mandatory, for metadata as well as for delivered files. What that looks like in practice is described in fileadmin on S3 with caching.

Buckets should be separate per tenant, with versioning and their own access role. Uploads should also be scanned for malware before they are delivered.

04 — Read-only code, setup once per release, one scheduler

Once all state lives outside, the code can be read-only. The container's root filesystem is read-only. Only selected directories are writable, for example a tmpfs for temporary files. An attacker who can execute code cannot change it permanently.

Setup and migrations run neither in the main container nor in every pod, but once per release in a hook before the rollout. A lock in the database ensures that two migrations never run against each other, and if the hook fails, the Deployment stays as it is. The pod itself only copies the code caches pre-warmed in the build and is ready within seconds. The scheduler becomes its own CronJob with concurrencyPolicy: Forbid, so no task runs twice. And the cache flush after a deployment becomes a hook that fires exactly once after the rollout. How a pod's start went from 54 to twelve seconds this way is covered in the field note on pod start.

 

apiVersion: batch/v1
kind: CronJob
metadata:
  name: typo3-scheduler
spec:
  schedule: "*/5 * * * *"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          automountServiceAccountToken: false
          initContainers:
            # same verification chain as the web pod (part 3), shortened, key volume omitted
            - name: verify-signature
              image: registry.example.org/tools/cosign@sha256:...
              args: ["verify", "--key", "/keys/release.pub",
                     "registry.example.org/typo3/site@sha256:..."]
          containers:
            - name: scheduler
              image: registry.example.org/typo3-runtime@sha256:...
              workingDir: /app
              args: ["php", "vendor/bin/typo3", "scheduler:run"]
              volumeMounts:
                - {name: app, mountPath: /app, readOnly: true}
          volumes:
            - name: app
              image:
                reference: registry.example.org/typo3/site@sha256:...
                pullPolicy: IfNotPresent

 

The CronJob needs the same chain as the web pod from part 3. The runtime image contains no TYPO3 code. So here too, an init container verifies the signed artefact before the kubelet mounts it as a read-only image volume. Without that step, the scheduler either does not start at all or runs code nobody has verified.

Logs go to stdout and stderr, not to files inside the container. The platform collects them, and nothing is lost when a pod disappears. The install tool stays disabled in the cluster.

Mapping to IT-Grundschutz

Frequently asked questions

Isn't a ReadWriteMany volume enough for the whole TYPO3?+

Technically yes, operationally rarely. Network filesystems are slow with many small files. Caches, sessions and locks on a shared filesystem also create conflicts that are hard to track down. It is better to put each kind of state where it belongs.

Where should TYPO3 sessions live in a cluster?+

In a shared key-value store such as Valkey or Redis, protected with TLS and its own user. The core ships a Redis backend for sessions. The database works too, but burdens it unnecessarily with many frontend sessions.

How do I prevent duplicate scheduler runs?+

Take the scheduler out of the web pod and run it as its own CronJob with concurrencyPolicy: Forbid. It then runs exactly once, however many web pods are active.

Can the root filesystem really be read-only?+

Yes, once every writable path has been moved out deliberately: temporary files into a tmpfs, caches into Valkey or a cluster-capable backend, uploads into object storage. The first start with a read-only filesystem quickly shows whatever still wants to write.

Conclusion

Statelessness is the precondition for everything that follows: rolling updates, autoscaling, pods on several nodes. Sessions and locks in Valkey, files in object storage, caches with a backend that knows about several pods, setup in an init container. After that, every TYPO3 pod is replaceable, provided Valkey, object storage and the database are resilient themselves. A single Valkey without persistence signs out every editor when it restarts. The typical misconceptions on the way there are collected in Five misconceptions: TYPO3 on Kubernetes.

How to put that replaceability to work is shown in the next part: autoscaling and probes. Back to part 7: secrets without Git.

I get your TYPO3 ready for several pods. Sessions, files, caches and scheduler go where they belong in a cluster.

An inventory of the state, the switch for sessions and locks, fileadmin in object storage with a cache, setup in an init container and a scheduler that runs exactly once.

Platform operations rather than advice on paper: if you want, I run the stateless setup for you on an ongoing basis, storage and cache included.

Book a call →

About the author

Photo of 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.