# 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.
> [!NOTE]
> HostKit is currently published as a beta. The core planning/apply workflow is
> usable and documented, but DSL, provider, and recipe APIs may still change
> before a stable release.
## Why HostKit
Infrastructure code should be boring Elixir, not an opaque pile of shell scripts.
HostKit gives you:
- **Declarative host bootstrap** — OS packages, accounts, directories, files, env files, systemd units, firewall rules, and `mise` runtimes.
- **Docker-less service isolation** — systemd sandboxing, resource limits, network policy, read/write path allowlists, loopback listeners, and managed env files.
- **Plan before apply** — read current state, produce a diff, write an inspectable JSON artifact, then apply exactly what was reviewed.
- **Distribution-aware packages** — semantic package names resolve through Repology and can be locked for deterministic applies.
- **No hidden Mix requirement on target hosts** — bootstrap can install prerequisites and BEAM tools through `mise`.
- **Host config in `.exs`** — syntax highlighting, macros, composition, and project-local DSLs.
- **Provider boundary** — integrations such as Caddy live as providers while core owns systemd/unitctl primitives.
- **Linux-native integration testing** — Incus containers/VMs replace macOS-only Lima flows on Linux.
## One file: host, runtime, isolated service, reverse proxy
The complete example lives in [`examples/full_host.exs`](examples/full_host.exs) and is loaded by the test suite so it does not drift.
```elixir
use HostKit.DSL, providers: [HostKit.Providers.Caddy]
project :prod do
host :app, at: "app.example.com" do
ssh do
user "root"
identity_file Path.expand("~/.ssh/id_ed25519")
accept_hosts true
retry attempts: 3
end
end
bootstrap do
package :ca_certificates
mise do
tool :erlang, "29.0.2"
tool :elixir, "1.20.1"
end
end
service :api do
account system: true
storage :data, mode: 0o750
env :runtime do
secret :database_url, env: "DATABASE_URL"
end
daemon do
env :runtime
exec ["/opt/api/bin/server"]
isolate do
memory_max "512M"
writable :data
network :loopback
end
listen :http, port: 4000
end
caddy_site "api.example.com" do
reverse_proxy :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. See the [DSL design guidelines](guides/reference/dsl-guidelines.md) for naming, block shape, defaults, and reference style.
Plan, review, apply:
```sh
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.
## Managed local demo instance
`host` is a connection endpoint. `instance` is a lifecycle-managed compute boundary. Backends such as Incus create/start/destroy the instance, while nested `host` and `service` declarations describe how HostKit connects into it and what should run inside.
```elixir
use HostKit.DSL
project :demo do
instance :demo_vm do
backend :incus, sudo: true
image "images:ubuntu/24.04"
kind :container
lifecycle :ephemeral
expose :ssh, host: 2222, guest: 22
expose :web, host: 18_080, guest: 80
host :guest, at: "127.0.0.1" do
ssh do
user "root"
password "hostkit-demo"
port 2222
accept_hosts true
end
end
service :web do
package :caddy
end
end
end
```
Manage the declared instance through the backend-neutral instance CLI:
```sh
mix host_kit.instance ensure demo_vm infra/demo.exs
mix host_kit.instance status demo_vm infra/demo.exs
mix host_kit.instance destroy demo_vm infra/demo.exs
```
See [`examples/livebook_demo_instance.exs`](examples/livebook_demo_instance.exs) for the local Livebook demo target used by the notebook workflow.
## Interactive notebook
Deploy real services from Livebook with Kino inputs for SSH target/auth, plan review, explicit apply, and HTTP verification:
Static Caddy site:
[](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Felixir-vibe%2Fhost_kit%2Fblob%2Fmaster%2Fnotebooks%2Flearn%2Fdeploy_caddy_site.livemd)
Phoenix app from Git, with pinned source revision and source-aware build stamps:
[](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Felixir-vibe%2Fhost_kit%2Fblob%2Fmaster%2Fnotebooks%2Flearn%2Fdeploy_phoenix_app.livemd)
The notebooks are self-contained and their deployment DSL cells are also exercised by the integration test suite.
## Documentation
- [Getting started](guides/introduction/getting-started.md)
- [Conventions and paths](guides/introduction/conventions-and-paths.md)
- [Remote bootstrap and plan artifacts](guides/deployment/remote-bootstrap.md)
- [Systemd isolation](guides/deployment/systemd-isolation.md)
- [Firewall and networking](guides/deployment/firewall-and-networking.md)
- [Workspaces and tenants](guides/workspaces/workspaces-and-tenants.md)
- [Observability and monitors](guides/operations/observability-and-monitors.md)
- [Timers and jobs](guides/operations/timers-and-jobs.md)
- [Deploy a Caddy site Livebook](notebooks/learn/deploy_caddy_site.livemd)
- [Deploy a Phoenix app Livebook](notebooks/learn/deploy_phoenix_app.livemd)
- [CLI reference](guides/reference/cli.md)
- [Full DSL/reference notes](guides/reference/full-reference.md)
- [Internal architecture](guides/reference/internal-architecture.md)
- [Changelog](CHANGELOG.md)
## Development
```sh
mix deps.get
mix ci
```
Run the Incus-backed remote integration on Linux:
```sh
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