CoDNA Request access
Contents

Docs Decisions

0011 · OCI containers, Podman first, three deployment strategies

Status: Accepted · 2026-09-22

Context

CoDNA is self-hosted software. How it is installed is a feature, not an afterthought — the people evaluating it are infrastructure engineers deciding whether it is worth their afternoon.

Three constraints shaped this:

  1. Companies running Docker Desktop at any scale need a paid license. An open-source project should not implicitly require one.
  2. Teams already run Kubernetes, or they run a VM, or they want one host with Compose. All three are legitimate and none is the "real" way.
  3. During development, Docker Desktop on this machine could not reach Docker Hub at all: DNS returned AAAA records and the host had no IPv6 default route. Podman's VM had no such problem. A stack that only works on one runtime is a stack that sometimes does not work.

Decision

Target OCI, not a vendor. The build file is Containerfile, which podman, docker, buildah, and kaniko all consume unchanged. The compose file is compose.yaml following the Compose Specification, so podman compose and docker compose both run it. No runtime-specific features are used anywhere.

Podman is the documented default, with docker shown alongside everywhere. make takes CONTAINER=docker to switch.

Three supported deployment strategies, all running the same binary:

Strategy Artifacts
Compose compose.yaml, with CoDNA itself behind an app profile
Kubernetes deploy/kubernetes/ raw manifests plus kustomization, and deploy/helm/codna/
VM / bare metal deploy/systemd/ unit, sandboxed, plus an env template

Migrations are embedded in the binary. codna migrate up runs the schema from a copy compiled in at build time.

Consequences

  • No deployment ever ships migration files or installs a migration tool, and the schema can never disagree with the binary running against it.
  • The image is ~15 MB: a static binary on distroless, running as uid 65532 with a read-only root filesystem and no shell. There is nothing to exec into.
  • Migrations run as a Job or ExecStartPre, never an initContainer on a multi-replica Deployment. goose takes no lock by default, so two replicas starting together could apply the same migration twice.
  • Liveness and readiness are genuinely different endpoints. /healthz checks only that the process is up; making it check Postgres would turn a database blip into a restart of every pod, converting a recoverable dependency outage into an outage of CoDNA as well. /readyz checks dependencies so an unhealthy pod leaves the Service instead of serving errors.
  • The local storage driver pins a deployment to one replica. Ingested originals need a writable volume, and ReadWriteOnce cannot be shared — hence strategy: Recreate. CoDNA is otherwise replica-safe: the outbox worker and scheduler both claim work with FOR UPDATE SKIP LOCKED. Scaling out needs ReadWriteMany or an object-store driver.
  • The IANA timezone database is compiled into the binary (_ "time/tzdata"), because scheduled automations carry timezones and a distroless or scratch base may ship no zoneinfo.
  • Three deployment paths are three things to keep working. Accepted: make deploy-check validates the manifests and the chart.

Alternatives rejected

Docker only. Simpler documentation and the widest familiarity. Rejected on the licensing question for commercial users, and because it failed outright on the first machine this project was built on.

Helm only, no raw manifests. Fewer artifacts. Rejected because it forces Helm on people who use kustomize, ArgoCD with plain manifests, or just kubectl apply.

Shipping Postgres and Typesense in the default Kubernetes install. Friendlier first run. Rejected as the default because a single-pod Postgres with no backups or failover is the wrong place for a company's entire knowledge base; it is available as an opt-in file for evaluation.

Distributing only a container image. Rejected because a VM with systemd is still how a great many companies run internal tools, and a static Go binary makes supporting that nearly free.