# Remote bootstrap and plan artifacts
HostKit's remote flow is designed for machines that may not have Elixir, Mix, or application runtimes installed yet.
The control machine runs HostKit. The target machine only needs SSH and a supported package manager.
## Declare the host
```elixir
use HostKit.DSL
project :prod do
host :server do
hostname "203.0.113.10"
user "root"
sudo true
ssh password: secret_env("HOSTKIT_SSH_PASSWORD"),
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
end
```
Identity-file auth is preferred when available:
```elixir
ssh identity_file: Path.expand("~/.ssh/id_ed25519"),
silently_accept_hosts: true
```
## Package resolution and locks
HostKit resolves semantic package names to target package names using Repology. Write a package lock when planning for repeatable applies:
```sh
mix host_kit.plan --host server \
--write-package-lock host_kit.package.lock \
infra/config.exs
```
Apply with the lock:
```sh
mix host_kit.apply --host server \
--package-lock host_kit.package.lock \
--confirm \
infra/config.exs
```
## Plan artifacts
For production, prefer a two-step artifact flow:
```sh
HOSTKIT_SSH_PASSWORD='...' mix host_kit.plan --host server \
--package-lock host_kit.package.lock \
--out host_kit.plan.json \
infra/config.exs
```
Review `host_kit.plan.json`, then apply the reviewed artifact:
```sh
HOSTKIT_SSH_PASSWORD='...' mix host_kit.apply --host server \
--plan host_kit.plan.json \
--confirm \
infra/config.exs
```
Artifacts include target metadata such as package manager/repository. `apply --plan` validates that metadata before applying package changes.
## Secret safety
`secret_env/1` serializes as a reference:
```json
{
"$type": "struct",
"module": "Elixir.HostKit.Secret",
"fields": {
"source": {
"$type": "tuple",
"items": [
{"$type": "atom", "value": "env"},
"HOSTKIT_SSH_PASSWORD"
]
}
}
}
```
The resolved secret value is not stored in the artifact.
## Linux integration with Incus
Create an Incus-backed target:
```sh
HOSTKIT_INCUS_SUDO=true HOSTKIT_SSH_PUBLIC_KEY=$HOME/.ssh/id_ed25519.pub \
scripts/incus_integration_vm.sh ensure
```
Run the remote CLI integration against it:
```sh
HOSTKIT_INTEGRATION_TOOL=incus HOSTKIT_INCUS_SUDO=true \
mix test test/integration/cli_remote_test.exs --include integration
```
Use `HOSTKIT_INCUS_TYPE=vm` for an Incus VM instead of the default container.
## Real remote validation
Copy `examples/integration_hosts.example.exs`, set the hostname/auth settings, then run:
```sh
HOSTKIT_SSH_PASSWORD='...' \
HOSTKIT_INTEGRATION_TOOL=remote \
HOSTKIT_INTEGRATION_CONFIG=examples/integration_hosts.example.exs \
mix test test/integration/cli_remote_test.exs --include integration
```