Composable, mesh-native appliances on Debian.
Every appliance is secure by default and can join a private cloud on day one.
A fork of TurnKey Linux 19 (Debian 13) that turns single-machine appliances into building blocks of a real cloud: small components that run alone or together, replicate across sites, fail over, and are upgraded with apt. IPv6-first, so every node has a routable address of its own, and declarative, so every machine is described by one YAML file that Keel applies and re-applies to the same result.
- IPv6-first
- Declarative
- WireGuard mesh
- Upgraded with apt
- WireGuard tunnel
- opened from inside a NAT
- traffic between nodes
Six things an appliance should already do
TurnKey appliances are excellent on day zero and built for one machine. Keel keeps the appliances and changes the model around them. Each point says how far it is built.
-
Decided
Composed, not pre-cooked
PostgreSQL, MariaDB, Redis, Nginx and the language runtimes are components of their own, each able to run alone or serve another appliance. An appliance is a manifest that names what it is built on. LAMP and LAPP stop being images and become a recipe.
-
Partly built
The mesh is built in
WireGuard ships in Keel Core, so every appliance can join a mesh, even alone. Tunnels are opened from the inside out, so a node behind NAT needs only outbound UDP. Native IPv6 goes direct,
2001:db8:a::10to2001:db8:b::10; IPv4-only nodes enter through a rendezvous point that translates. -
Partly built
Secure by default
Layers are exported with no host keys, certificates or machine-id, and each machine makes its own at first boot. CrowdSec ships in Core; Keel Web adds a WAF and Anubis. Every listening port is declared loopback, mesh or public, and the firewall is derived from that.
-
Partly built
A real cloud, not a single box
A primary and a hot standby, the standby seeded from the primary and kept read-only. Failover is the operator's call, and automatic failback after full catch-up is a goal. Directories replicate over the mesh, and an etcd registry records every node and its site.
-
Working today
One file is the truth
A YAML instance spec declares the machine: its name, IPv6 network, certificates, users, database role and monitoring.
keel inspectwrites it from a running machine,keel diffshows drift field by field, andkeel spec applyconverges. Re-applying changes nothing. -
Decided
Upgraded with apt, backed up off-site
Everything Keel installs becomes a
.debin the Keel repository, on a stable or a testing track declared in the YAML. A database primary hands over before it upgrades. Backups go to a Keel Backup appliance in another site, and restores are tested on a schedule.
Small components, joined by the mesh
An application sits on a runtime, the runtime on Keel Web, and Keel Web on Keel Core. The data services stand beside the stack as appliances of their own, and the WireGuard mesh joins them.
An appliance, top to bottom
- ApplicationWordPress, Odoo, Nextcloud, Mastodon: a manifest and an inithookplanned
- RuntimePHP-FPM, Python, Ruby, Node.js or Goplanned
- Keel WebNginx, the Coraza WAF and Anubisfocus now
- Keel CoreDebian 13, WireGuard, etcd, CrowdSec, the installerfocus now
Data services, beside it
- PostgreSQL, MariaDBa primary and a hot standby
- Redissessions, queues and caches
- Searchderived data, rebuilt when a node joins
- Object storageGarage, for media, attachments and backups
In a simple installation the data services are embedded on the same machine. In an advanced one they are appliances of their own, found on the mesh. The composition in detail.
One image, three ways to install it
The image is the same in every mode; the mode chosen at first boot decides what runs. Decided, 0028
-
Simple
- Everything in one LXC
- No mesh and no etcd
- CrowdSec installed and disabled
- Keel Web is Nginx only
-
Advanced, cloud simple
- The database replicated from a primary to a standby
- The application's files in a single LXC
- The primary hands the standby its key, password and WireGuard endpoint
- Two nodes cannot elect, so failover is by hand
-
Advanced, cloud advanced
- Three LXC at least, with an etcd quorum
- Directory replication over the mesh
- Three sites, or two plus an external arbiter
- Later nodes install by discovery, not by typing
Three gates before the application
In an advanced installation, a request meets CrowdSec first, then Nginx with the WAF inline, then Anubis, and only then the application. Decided, 0030
- InternetA request arrives, over IPv6 first.
- CrowdSecBans known bad addresses before anything else runs.
- Nginx + WAFTerminates TLS, serves static files and runs Coraza with the OWASP Core Rule Set.
- AnubisProof of work, behind Nginx and never at the edge. Search crawlers are exempt.
- ApplicationOnly what passed the gates reaches it.
Keys, the pipeline, exposure classes and what each mode enables.
Declared in one file, converged by one command
The instance spec, /etc/keel/instance.yaml, is the machine written down. Keel reads the machine into it, compares the two and converges the machine to it.
- Inspect. Write a spec from a running machine, every field with its source, secrets never read.
- Diff. Report drift field by field: same, drift, unknown or not declared.
- Apply. Converge only what differs. A second run changes nothing.
- Emit. Every installation will write its complete YAML, defaults included, so one answer file replays many installations. Decided, 0027
$ keel inspect --output /etc/keel/instance.yaml instance.hostname: blog (from /etc/hostname) instance.fqdn: shop.example.org (from /etc/hosts) network.interfaces.eth0.ipv6: static 2001:db8:1::10/64 gateway fe80::1 inspect: 20 inferred, 1 not inferred (0 required), 2 secrets to provide; spec complete # declare the name this machine should have $ sed -i 's/shop.example.org/blog.example.org/' /etc/keel/instance.yaml $ keel diff instance.hostname: same (blog) instance.fqdn: drift (declared blog.example.org, observed shop.example.org) network.interfaces.eth0.ipv6.address: same (2001:db8:1::10/64) diff: 12 same, 1 drift, 0 unknown, 1 not declared, 2 not compared; drift found $ keel spec apply --system-only apply --system-only: /etc/inithooks.conf not read or written instance.fqdn: write /etc/hosts with '127.0.1.1 blog.example.org blog' (mode 0644): done apply --system-only: 1 change(s), 0 failed $ keel diff | tail -n 1 diff: 13 same, 0 drift, 0 unknown, 1 not declared, 2 not compared; no drift
Commands and output lines as the keel documentation gives them; the session is abridged.
Signed, content-addressed layers
Every appliance is assembled from read-only layers. Each is a deterministic tarball with a sha256, listed in a plain-text layer manifest that is signed by the project key and tied to a git commit.
- Reproducible. The same commit and the same manifest give the same bytes.
- Incremental. An update downloads only the layer that changed: a new WordPress release is the app layer, not the base.
- Verified.
keel pullchecks every digest, andkeel verifychecks installed layers against their manifests.
Measured on the build host
Unmodified TurnKey 19.0 recipes built as layers, before the composition redesign. One build host, one measurement each.
Rules every change follows
-
90 percent test coverage minimum, 95 for project code
Every change ships with tests. The forked repositories keep at least 90 percent coverage; code the project writes keeps 95 percent. The check is required on every pull request.
-
IPv6 in every example
Commands, defaults and configuration examples use IPv6 addresses, such as
[2001:db8:4b1::10]:22. IPv4 is optional and never assumed. -
English
Commits, code comments, documentation and pull requests are written in English.
-
History is never rewritten
Forks keep the upstream history and the upstream remote. No squashing, no force pushes, cherry-picks with attribution.
-
System containers only
Full init, journal, cron, SSH and filesystem-level backup. Tests run on LXC with native systemd against real databases. No other container model is involved.
Signed testing images, and the first two mesh nodes
What landed in two days of work. Everything here is on the testing channel and not meant for production.
-
Shared default keys, withdrawn and fixed
The earlier test images and layers carried keys made at build time, so every machine made from one shared them. They were withdrawn and rebuilt. A scan on every layer and image now checks that none carries SSH host keys, TLS private keys, a Webmin certificate or snakeoil keys, and every machine generates its own at first boot.
-
A signed archive with two tracks
archive.keellinux.org is signed and has two tracks:
trixie-testing, where everything lands first, andtrixie, the stable track. Promotion to stable is a maintainer's act. -
Debian and the Keel archive, nothing else
Images are built from Debian and the Keel archive only, with no TurnKey archive. The TurnKey tools Keel still uses, turnkey-ssl, netinfo, conffile, sysinfo, version, tkl-installer and the dhcpcd glue, are now Keel forks, and turnkey-ssl no longer ships a key. TKLBAM and the TurnKey Hub agent are gone; Keel Backup and Keel Cloud replace them later.
-
Keel Web serves HTTPS from the start
In simple mode Keel Web serves HTTPS out of the box with the machine's own certificate, a Keel placeholder page and Keel error pages. Let's Encrypt is set up from the console, prefilled from the instance spec. Cloud mode adds the Coraza WAF and the Anubis bot challenge.
-
A first boot that finishes
First boot asks for the FQDN and offers to keep a root password the hypervisor already set. Unattended, it runs to the end without hanging, and it installs security updates from Debian Security without ever blocking the boot.
systemctl --failedis clean in containers. -
The mesh: two nodes so far
Phase 5 has started. Two Keel Web nodes in different locations are joined over a WireGuard overlay, declared in each node's instance spec and confirmed through the network safety window. Next is a one-command join,
keel mesh invitethenkeel mesh join(decision 0048, in review), and etcd forms at the third node.
In testing, and plain about it
There is no release for end users. The current images are signed testing pre-releases, not meant for production: Keel Core 19.0-8 and Keel Web 19.0-3, built on 3 October 2026. Each is a .tar.zst for LXC, on its GitHub release and on mirror.keellinux.org, and listed in the Proxmox template index. Decision 0043 makes the ISO the second release format.
Packages come from archive.keellinux.org, signed, on a testing track that everything reaches first and a stable track the maintainer promotes to.
The build host reproduces unmodified TurnKey 19.0 from the forked repositories, which is the baseline every change is measured against. keel inspect, keel diff and keel spec apply run on the test images, and a MariaDB pair, with seeding and a read-only replica, is mostly done in keel 0.11.
The composition architecture on this site was decided on 30 September 2026. Where a point says decided or goal, it is on the roadmap and not yet in the code.
The focus now
- P5The mesh
Two Keel Web nodes are joined over WireGuard, declared in each node's spec. Next: a one-command join, and etcd at the third node.
- P1Keel Core and Keel Web
In testing as signed pre-releases, with the appliance manifest. Keel Web serves HTTPS in simple mode; Coraza and Anubis come with cloud mode.
- P4WordPress on Keel PHP
The first application built from a manifest, with no Apache in the image.
Decisions: 0023 to 0040 on the tracker, and the manifest, tracker#39.
Read how it fits together
The architecture, the security model and the cloud are each on a page of their own, with every claim tied to the code or to a decision on the tracker.