HostKit declarations are ordinary .exs files. The DSL evaluates to plain HostKit structs; evaluation does not apply changes.
Install
Add HostKit to a Mix project while it is unreleased or path-based in this workspace:
def deps do
[
{:host_kit, path: "../host_kit"}
]
endThen fetch deps:
mix deps.get
Create a host config
# infra/config.exs
use HostKit.DSL
project :demo do
host :local_vm, at: "192.0.2.10" do
ssh do
user "root"
identity_file Path.expand("~/.ssh/id_ed25519")
accept_hosts true
end
end
bootstrap do
package :ca_certificates
package :curl
mise do
tool :erlang, "29.0.2"
tool :elixir, "1.20.1"
end
end
endPlan
mix host_kit.plan --host local_vm infra/config.exs
For deterministic package resolution, write a lock file:
mix host_kit.plan --host local_vm \
--write-package-lock host_kit.package.lock \
infra/config.exs
Review a plan artifact
mix host_kit.plan --host local_vm \
--package-lock host_kit.package.lock \
--out host_kit.plan.json \
infra/config.exs
host_kit.plan.json is JSON, not an opaque binary. Review it before apply.
Apply
mix host_kit.apply --host local_vm \
--plan host_kit.plan.json \
--confirm \
infra/config.exs
Use --dry-run instead of --confirm to exercise the apply path without changing the target.
Secrets
Control-plane secrets are represented as references:
ssh do
user "root"
password secret_env("HOSTKIT_SSH_PASSWORD")
accept_hosts true
endHostKit resolves the environment variable only when opening the SSH connection. Plan artifacts contain HOSTKIT_SSH_PASSWORD, not the password value.
Target application env files use contextual env declarations:
service :app do
env :runtime do
set :mix_env, :prod
secret :database_url, env: "DATABASE_URL"
end
daemon do
env :runtime
exec ["/opt/app/bin/server"]
end
end