HostKit

Elixir-native host management: declare a Linux host, bootstrap packages and runtimes, isolate services with systemd, wire provider integrations, review a plan artifact, then apply it locally or over SSH.

HostKit is for operating real machines without assuming the target already has Elixir, Mix, Docker, or your application runtime installed.

Why HostKit

Infrastructure code should be boring Elixir, not an opaque pile of shell scripts.

HostKit gives you:

One file: host, runtime, isolated service, reverse proxy

The complete example lives in examples/full_host.exs and is loaded by the test suite so it does not drift.

use HostKit.DSL, providers: [HostKit.Providers.Caddy]
project :prod do
host :app do
hostname "app.example.com"
user "root"
sudo true
ssh identity_file: Path.expand("~/.ssh/id_ed25519"), silently_accept_hosts: true
end
service :bootstrap do
package :ca_certificates
mise path: "/usr/local/bin/mise", system_data_dir: "/usr/local/share/mise" do
tool :erlang, "29.0.2"
tool :elixir, "1.20.1"
end
end
service :api do
account "api", system: true, home: "/var/lib/api"
directory "/var/lib/api", owner: "api", group: "api", mode: 0o750
env_file "/etc/api/api.env", owner: "root", group: "api" do
secret :database_url, env: "DATABASE_URL"
end
daemon "api.service" do
service_user "api"
environment_file "/etc/api/api.env"
exec_start ["/opt/api/bin/server"]
sandbox :strict_app,
resources: [memory_max: "512M"],
sandbox: [read_write_paths: ["/var/lib/api"]]
listen :http, port: 4000, on: :loopback
wanted_by :multi_user
end
caddy_site :api, "api.example.com" do
reverse_proxy listener(:http)
end
end
end

This compiles to inspectable HostKit structs and renders ordinary Linux primitives: packages, files, env files, accounts, systemd units, Caddy site config, and systemd hardening directives such as NoNewPrivileges=, ProtectSystem=, RestrictAddressFamilies=, ReadWritePaths=, and memory limits.

Plan, review, apply:

mix host_kit.plan --host app \
--write-package-lock host_kit.package.lock \
--out host_kit.plan.json \
infra/config.exs
mix host_kit.apply --host app \
--plan host_kit.plan.json \
--confirm \
infra/config.exs

secret_env/1 stores an environment-variable reference. Plan artifacts include the variable name, not the resolved secret value.

Interactive notebook

Deploy real services from Livebook with Kino inputs for SSH target/auth, plan review, explicit apply, and HTTP verification:

Static Caddy site:

Run Caddy notebook in Livebook

Phoenix app from Git, with pinned source revision and source-aware build stamps:

Run Phoenix notebook in Livebook

The notebooks are self-contained and their deployment DSL cells are also exercised by the integration test suite.

Documentation

Development

mix deps.get
mix ci

Run the Incus-backed remote integration on Linux:

HOSTKIT_INCUS_SUDO=true HOSTKIT_SSH_PUBLIC_KEY=$HOME/.ssh/id_ed25519.pub \
scripts/incus_integration_vm.sh ensure
HOSTKIT_INTEGRATION_TOOL=incus HOSTKIT_INCUS_SUDO=true \
mix test test/integration/cli_remote_test.exs --include integration

Status

HostKit is early and intentionally evolving. Runtime APIs come first; Mix tasks wrap them. DSLs compile to plain structs so plans and artifacts remain inspectable.

License

MIT