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 do
hostname "192.0.2.10"
user "root"
sudo true
ssh identity_file: Path.expand("~/.ssh/id_ed25519"),
silently_accept_hosts: true
end
service :bootstrap do
package :ca_certificates
package :curl
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
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 password: secret_env("HOSTKIT_SSH_PASSWORD"),
silently_accept_hosts: trueHostKit 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 the env-file DSL:
env_file "/etc/app/app.env" do
set :mix_env, :prod
secret :database_url, env: "DATABASE_URL"
end