Skip to main content

Minimum system requirements

What a machine needs to run Kiln, to build the release image, and to develop on it. Written against v1.1.0. The versions here restate .tool-versions and the Dockerfile; when they disagree, those files win and this page needs editing (mix kiln.toolchain.check fails when the two of them drift from each other).

Running the release image

The prebuilt image (ghcr.io/the-verscienta/kiln_cms, one per version tag) carries the release and every native library it needs. A deployment adds Postgres, a reverse proxy, and somewhere for media to live.

MinimumRecommendedNotes
CPU1 shared vCPU2 vCPUThe BEAM uses every core it is given; one is enough for a small site. The Fly and Render specs use one
RAM512 MB1 GB512 MB runs a small site (the Render blueprint's 0.5c-512mb plan); the Fly, Render and DigitalOcean specs use 1 GB. Image processing (libvips) and in-app backups are the spikes
DiskImage plus mediaA volume or object storage for mediaUploads live on the container's local disk unless KILN_MEDIA_ROOT points at a mounted volume or S3_BUCKET is set; a redeploy discards the local disk. The Render blueprint mounts 1 GB at /app/media
Postgres17 with the vector extension17, pgvector/pgvector:pg17The only required service. Older majors are not tested. Extensions created on migrate: citext, pg_trgm, unaccent, vector; a server without vector available fails at mix ash.setup with "extension vector is not available"
Postgres poolPOOL_SIZE=10Sized per performance.mdThe pool is shared by web requests and Oban workers, so a busy instance wants more
Reverse proxyTLS termination, WebSocket pass-throughLiveView and the API sockets need WebSockets forwarded; the proxy must set x-forwarded-for and x-forwarded-proto. See deploy.md
Outbound network443Webhooks, oEmbed, the update feed, image imports and the CDN purge all go out over HTTPS through KilnCMS.SafeFetch. Port 25 outbound only for MAIL_MODE=direct (the built-in MTA); most clouds block it, see direct-email-delivery.md
Database TLSOn by defaultssl: true; set DATABASE_CACERTFILE to verify the server certificate

Native libraries in the runner. These are installed in the image and are what a from-source deployment has to provide itself:

LibraryUsed forIf missing
libvips 8.x (libvips42t64 on Debian trixie)Every image derivative and transformUploads fail to derive; nothing else runs
qpdfPDF metadata stripping on uploadA PDF that cannot be stripped is refused (a privacy control that silently does not apply is worse than none), see media-pipeline.md
postgresql-client-17 (pg_dump)In-app backupsBackups unavailable. Must match the server's major version; see backups.md
ffmpegA/V duration, poster frames, A/V metadata strippingOptional and not in the image. Without it audio and video upload without enrichment, and REQUIRE_AV_METADATA_STRIP turns a missing strip into a refusal
openssl, ca-certificates, locales (en_US.UTF-8)TLS, outbound HTTPS, the BEAM's string handlingBoot failures

Optional services. None are required, and the dev and prod compose files keep each behind a profile:

ServiceProfileWhat it adds
S3-compatible object storage (MinIO in compose)s3Media that survives redeploys and serves from a CDN. Any S3 API works
Meilisearch v1.11searchAn alternative search backend; Postgres full-text is the default and is what search is tuned on
Dragonfly / RediscacheNothing today: Kiln's caches are in-process by decision (D2) and cross-node busts travel over native PubSub. Kept for parity only

Multi-node needs no broker: PubSub is native, Oban is Postgres-backed, and node discovery is DNS_CLUSTER_QUERY. It does need long node names (RELEASE_NODE=kiln_cms@<ip>) and a shared media store; see deploy.md.

The ML stack is not in the default image. Semantic search and the reranker need a KILN_ML=1 build with Bumblebee, Nx and EXLA; that build is larger; the plan does not yet size it, so budget RAM for the model separately. A default build runs keyword search and reports KilnCMS.Search.ML.available?/0 as false.

Presets

Three environment files under deploy/presets/ turn the runtime features on or off as a set. Each layers on .env.prod, which keeps the required variables and the secrets; a preset holds neither.

PresetImageWhat is onNeeds from the host
minimal.envkiln_cms:1.xConsole, delivery, backups, media on a volume. Nothing calls out and no mail leaves512 MB, Postgres, a volume at /app/media
publishing.envkiln_cms:1.xMail over SMTP, oEmbed cards, provenance signing, CDN purge hooks, error tracking1 GB, Postgres, an SMTP relay, outbound 443; optionally S3 and a CDN
everything.envkiln_cms:1.x-mlpublishing plus Meilisearch, federation, experiments, the metrics listener, the LLM assistants and reranking2 GB, Postgres, SMTP, S3, Meilisearch, an LLM provider key

The only thing an image decides is whether the ML stack is compiled in (KILN_ML); every other optional subsystem is in every image and the preset switches it. The -ml tag is #1932; until it ships, everything.env runs on the lean image with reranking and semantic search off.

docker compose -f docker-compose.prod.yml \
  --env-file .env.prod --env-file deploy/presets/publishing.env up -d

Building the release image

A docker build on a laptop, a CI job, or a PaaS that builds from the Dockerfile (Coolify, Render) all do the same thing.

MinimumRecommendedNotes
RAM2 GB (marginal)4 GBPeak is mix deps.compile, not the app: the Ash ecosystem compiles in one BEAM. The Dockerfile caps the build BEAM to two schedulers so a small host is not OOM-killed
Disk3 GB free6 GBThe builder stage pulls the dep tree and node_modules; the final image is a slim Debian trixie with the release only
ToolchainPulled by the Dockerfilehexpm/elixir:1.20.4-erlang-29.1.1 on Debian bookworm as the builder, Node 22.23.3 installed into it; nothing to install on the host but Docker
NetworkHex, npm, GitHub, Debian and PostgreSQL apt mirrorsA build behind a proxy needs HEX_MIRROR and npm config as usual

Developing on it

RequirementNotes
Elixir / OTP1.20.4 on OTP 29.1.1, exactly as .tool-versions says; mix.exs requires ~> 1.20The floor is 1.20 rather than 1.19 because 1.20's mix format breaks some lines differently, so a tree formatted on one fails the other's formatting check. CI and the release image run the same pins, so the dialyzer that runs in CI is the one you run locally
Node.js22.23.3, pinned in .tool-versions and read by CI and the DockerfileOnly for building assets: the editor bundles TipTap from assets/node_modules. Not needed at runtime
Postgres17 with vector, via docker compose up -d postgresThe compose file runs pgvector/pgvector:pg17. Two test suites running at once against one server can exhaust Postgres's default 100 connections; raise max_connections or run them one at a time
DockerAny recent Docker or compatibleFor Postgres and the optional profiles; the app itself runs on the host
libvipsbrew install vips / apt install libvips-devvix builds against it; image tests need it
Build toolsbuild-essential / Xcode command-line toolsbcrypt_elixir, picosat_elixir, vix and y_ex compile native code
Checkout pathNo spacespicosat_elixir's Makefile splits absolute paths at a space; the error blames a missing gcc. mix setup refuses such a path up front
RAM4 GBA full mix compile with tests and dialyzer; KILN_ML=1 wants more
Disk1 GB for a lean dep tree; 2 GB with KILN_ML=1deps/exla alone is most of the difference

mix setup runs deps.get, ash.setup and assets.setup, and mix kiln.toolchain.check confirms the installed Elixir and OTP match the pins. A running instance reports its own build, commit and toolchain on /editor/system.

Browsers

The console is built on LiveView and Tailwind 4 and targets current evergreen browsers; there is no stated floor older than that. The public site a Kiln renders is plain HTML and works everywhere the theme does.