A static blog on Kubernetes with no registry, no database, and no persistent volume
This is the post about the site you're reading. I wanted to publish a static blog on the cluster without three things the "obvious" path drags in: a container registry to push a custom image to, a CI pipeline to build that image, and a persistent volume to hold the rendered site. None of them are necessary when the content is fully reproducible from git. Here's the shape, and why each piece is missing on purpose.
The idea: the pod builds itself at startup
There is no custom image. The running Pod assembles the site from git every time it starts, using a chain of initContainers over a shared scratch volume, then a stock web server serves the result:
- clone — a stock
gitimage shallow-clones the repo into anemptyDir. - build — a stock static-site-generator image renders the site, in place, inside that clone.
- copy — a tiny image copies the rendered output into a second
emptyDir. - serve — a stock
nginximage serves that second volume, read-only.
initContainers: git-clone → ssg-build → copy-output
└──── emptyDir: src ────┘ │
emptyDir: site
container: nginx (mounts emptyDir: site, readOnly)
Publishing is just git push. CI does one thing: kubectl rollout restart
the Deployment. New Pods come up, re-run the init chain, and serve the latest
commit. The "build" happens in the Pod, at start, from upstream images.
Why each thing is absent — the actual decisions
No registry. You only need a registry if you're baking a custom image. Here
every image is stock and upstream (git, the SSG, nginx); the only thing that
varies — your content — is injected at runtime by cloning it. Nothing to build,
nothing to push, nothing to store, nothing to keep patched yourself.
No persistent volume. emptyDir is exactly right when the data is
disposable and reproducible. The site is regenerated from git on every start, so
there is nothing worth persisting. The Pod is genuinely stateless: kill it, it
rebuilds identically. PVs exist to keep data a Pod can't regenerate; this Pod
can regenerate everything.
No CI image build. Because the build runs in the Pod, CI has no image step. It triggers a rollout and stops. The cluster, not the CI runner, is the build host — which also means the build environment is the same upstream image every time, pinned by tag.
Gotchas worth keeping
- Build into the SSG's default output dir, not a mounted path. Some
generators wipe the output directory before writing (a
--force-style clean). If that directory is a mount point, the wipe fails. So build inside the cloned tree andcopythe result into the served volume as a separate step — that's why there are twoemptyDirs and a copy stage, not one shared mount. - Replicas each rebuild independently. With two replicas and a rolling restart, each Pod clones and builds on its own. There's a few seconds of version skew mid-rollout. For a blog that's fine; for anything transactional it wouldn't be.
- Private repo = read-only token in a Secret. The clone uses a scoped, read-only credential pulled from a Kubernetes Secret. No write access, nothing baked into an image.
The honest trade-offs
This is the right tool only when the build is cheap and the content is git-native. What you pay for the simplicity:
- Slower Pod start — every start pays clone + build, instead of pulling a prebuilt image.
- No build gate — a broken commit produces a broken build inside the Pod. If that matters, also build in CI (purely as a validation step) or gate the rollout on a readiness probe so a failed build never serves.
- Doesn't scale to heavy builds or huge sites — a multi-minute build on every Pod start is the wrong place to be; that's when a real image pipeline earns its keep.
Takeaway
When your artifact is fully reproducible from git, you can collapse the whole
registry + PV + CI-image stack into stock images + initContainers + rollout restart. The Pod becomes the build host and git becomes the only source of
truth. It's not the answer for every workload — but for a static site it removes
three moving parts you'd otherwise own, patch, and debug forever.