---
title: "Docker"
description: "The three image variants, how to pull them, and how Kiln behaves inside a container."
---

> Documentation Index
> Fetch the complete documentation index at: https://kiln.wbxdocs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Docker

Kiln provides three Docker images that use the same configuration model and native media modules. Choose based on two needs: the FFmpeg compatibility fallback, and the database and admin console.

## The three variants

| Image | Capabilities | Default packager | FFmpeg | Purpose |
| --- | --- | --- | --- | --- |
|  | Config, login, M3U, playback | `native` | Not included | Minimal scratch runtime, 3.8 MB, no database |
|  | Full | `native` | Not included | Complete management and observability, natively only |
|  | Full | `auto` | Bundled | Native first, falls back when needed |

`latest` is an alias for `full`. All three run as the non-root uid/gid `999`, listen on `8080`, and hard-code `-config /etc/kiln/kiln.toml` as their command, so mounting the config at that path is all it takes to start with no arguments.

Lite creates no SQLite database, and its public surface is limited to `/healthz`, `/readyz`, `/v1/auth/login`, `/v1/playlist.m3u`, and `/v1/play/*`. An `auto` or `ffmpeg` packager, EPG, OTLP, or pprof in the config is rejected at startup rather than silently ignored. See [Image variants](/en/guide/variants/) for the full comparison.

> **Image defaults never override an explicit setting**
>
> `KILN_DEFAULT_PACKAGER_ENGINE` applies only when `[packager].engine` is absent. An explicit `auto`, `native`, or `ffmpeg` always wins, so the same config never changes behavior because of an image tag.

## Pulling an image

Images are published to the GitHub Container Registry:

```bash
docker pull ghcr.io/babywbx/kiln:lite     # stateless low-memory variant
docker pull ghcr.io/babywbx/kiln:core     # native packager, no ffmpeg
docker pull ghcr.io/babywbx/kiln:latest   # full, includes ffmpeg
```

A stable release publishes an exact version alongside moving tags:

| Tag shape | Meaning |
| --- | --- |
| `:1.0.0-lite`, `:1.0-lite`, `:1-lite`, `:lite` | Exact, minor, major, and newest release of the lite variant |
| `:1.0.0-core`, `:1.0-core`, `:1-core`, `:core` | The same set for core |
| `:1.0.0`, `:1.0`, `:1`, `:latest` | The same set for full, with `:latest` as its newest release |

A prerelease publishes only its exact version tag and never moves `lite`, `core`, `latest`, or the major and minor tags. Core builds for `linux/amd64`, `linux/arm64`, `linux/arm/v7`, and `linux/arm/v6`; lite and full build for `linux/amd64` and `linux/arm64`. All three get build provenance attestations pushed to the registry at release time.

## Minimal run

You need two files: a `kiln.toml` and a `kiln.keys`. The repository's `deploy/docker/kiln.docker.toml.example` already matches the image's path conventions, with `data_dir` at `/var/lib/kiln/data` and `keys_file` at `/etc/kiln/kiln.keys`, so copy it and change the origin and the account.

```bash
docker run --rm -p 8080:8080 \
  -v "$PWD/kiln.toml:/etc/kiln/kiln.toml:ro" \
  -v "$PWD/kiln.keys:/etc/kiln/kiln.keys:ro" \
  -v kiln-data:/var/lib/kiln/data \
  ghcr.io/babywbx/kiln:core
```

Lite targets fixed-configuration, low-resource playback nodes and runs fully read-only:

```bash
docker run --rm -p 8080:8080 --read-only \
  --cap-drop=ALL --security-opt=no-new-privileges \
  -v "$PWD/lite.toml:/etc/kiln/kiln.toml:ro" \
  -v "$PWD/kiln.keys:/etc/kiln/kiln.keys:ro" \
  -v kiln-lite-data:/var/lib/kiln \
  ghcr.io/babywbx/kiln:lite
```

`deploy/docker/lite.docker.toml.example` is the matching minimal config.

> **Do not pin the ffmpeg engine on a native-only image**
>
> Lite and core ship without ffmpeg. If a DASH channel resolves to the `ffmpeg` engine, `/readyz` returns 503 stating that the compatibility engine is unavailable, and the container never reaches a ready state. Switch the channel to `native`, or use the full image.

## Compose

`deploy/docker/compose.example.yaml` is a starting point you can edit directly. It builds the `full` target from the repository by default; to use a published image instead, drop the `build` block and set `image` to `ghcr.io/babywbx/kiln:core`.

```yaml title="compose.yaml"
services:
  kiln:
image: ghcr.io/babywbx/kiln:core
container_name: kiln
ports:
  - "8080:8080"
environment:
  KILN_PUBLIC_BASE_URL: "http://your-host:8080"
  KILN_LOG_FORMAT: "text"
  KILN_LOG_COLOR: "never"
volumes:
  - ./kiln.toml:/etc/kiln/kiln.toml:ro
  - ./kiln.keys:/etc/kiln/kiln.keys:ro
  - kiln-data:/var/lib/kiln/data
restart: unless-stopped

volumes:
  kiln-data:
```

Three details worth noting. Setting the log color to `never` keeps ANSI escapes out of `docker logs`. `KILN_PUBLIC_BASE_URL` controls the addresses written into the playlist and is required whenever the published port or hostname differs from what players use. And if the origin is another service on the same Compose network, point `base_url` at `http://service-name:port` and attach both containers to that network.

## Health checks

Neither `/healthz` nor `/readyz` requires credentials. `/healthz` means the process is alive; `/readyz` additionally verifies the compatibility engine, returning 503 only when the config contains a DASH channel resolving to the ffmpeg engine while ffmpeg is unavailable.

Every image carries a `HEALTHCHECK`: a 30-second interval, a 3-second timeout, a 10-second start period, and three consecutive failures before the container is marked unhealthy. Core and Full probe `/healthz` with `wget`. Lite is built on scratch and has no external commands at all, so it uses the binary's own `-healthcheck` subcommand for the same probe. That subcommand is useful on its own:

```bash
docker exec kiln /usr/local/bin/kiln -healthcheck http://127.0.0.1:8080/healthz
```

## Resource adaptation inside a container

`server.resource_mode` defaults to `auto`. At startup the process probes the real memory and CPU limits from cgroups and tightens the Go soft memory target, the segment memory budget, the maximum segment size, and pipeline concurrency accordingly. Detection covers cgroup v1 and v2, nested cgroups, limits inherited from a parent, and fractional CPU quotas, which means `docker run --cpus` and `--memory` are genuinely observed.

Adaptation only scales down: a lower value you configured is never raised. The startup log prints the probed values, the selected profile, and every effective budget, so you can confirm a container landed where you expected.

```bash
# Reproduce the low-resource plan
docker run --rm --cpus=1 --memory=192m --memory-swap=192m \
  -v "$PWD/resource-smoke.toml:/etc/kiln/kiln.toml:ro" \
  ghcr.io/babywbx/kiln:core
```

When host detection is wrong, override it with `KILN_RESOURCE_MEMORY_MB` and `KILN_RESOURCE_CPUS`; to opt out entirely, set `resource_mode = "performance"`. Profile tables and tuning advice live in [Operations](/en/guide/operations/).

## Outbound proxies

Kiln never rewrites the address on a route, so an address configured inside a container has to be reachable from inside it. For a proxy on the host, use `host.docker.internal` and give the container the matching `extra_hosts` mapping; for a proxy running as another container on the same Docker network, use its service name. Both topologies are written out in [Outbound proxies](/en/guide/proxy/#reaching-a-proxy-from-inside-a-container).

`[egress].docker_proxy_host` is a separate thing: it applies only when `[ffmpeg].mode = "docker"`, where it tells the ffmpeg container Kiln spawns how to reach the Kiln process itself. It has nothing to do with route addresses, and Kiln maps its default `host.docker.internal` to the gateway when it creates that container.

## Persistent data

`data_dir` is the only directory that needs to persist; the images default it to `/var/lib/kiln/data`. Core and full keep the SQLite database `kiln.db` there, along with the auto-generated session key at `auth/ed25519.pem` and the EPG cache when caching is enabled. The mount must be writable by uid/gid `999`, or the first start fails.

Lite creates no database: its `data_dir` holds only the auto-generated login key and transient media files. When running with `--read-only`, mount the writable volume at `/var/lib/kiln`.

Mount `kiln.toml` and `kiln.keys` read-only. The key file is fully validated once at startup, so changing it requires restarting the container.

Source: https://kiln.wbxdocs.com/en/start/docker/index.mdx
