This note records the intended boundary for HostKit release work before adding artifact download, activation, cleanup, or rollback behavior. For BEAM applications, ReleaseKit produces the deployment-neutral OTP release tarball and ETF manifest consumed by HostKit.Recipes.OTPRelease. Applications configure ReleaseKit prebuild steps, such as ReleaseKit.Step.Volt, in application config. HostKit can either consume an already-built manifest or, for local apply workflows, run the standard ReleaseKit artifact build before loading the manifest via otp_release ... release_kit: [...].
Today release/2 is intentionally small. It is a DSL helper that emits plain, inspectable resources:
- a versions directory, by default
path(:opt, "releases/<name>"), - a current symlink, by default
path(:opt, "current/<name>"), - the symlink target
<versions_dir>/<version>.
It does not fetch artifacts, build application artifacts, manage services, restart daemons, run readiness checks, prune old versions, or hide apply-time behavior.
service :gatus do
release :gatus, version: "5.36.0", owner: "deploy", group: "deploy"
daemon "gatus" do
service(exec_start: path(:opt, "current/gatus/gatus"))
end
endThe release declaration owns the filesystem pointer. Runtime declarations own how a daemon uses that pointer.
Keep these concerns separate:
-
Release/artifact concerns
- version identity,
- artifact source and checksum,
- unpack/build output path,
- active pointer path,
- provenance manifest,
- retention/garbage collection of inactive versions.
-
Runtime concerns
- systemd units,
- users/groups for processes,
- environment files,
- working directories,
- hardening/isolation,
- restarts and reloads.
-
Verification and monitoring concerns
- apply-time readiness resources,
- external monitor metadata,
- Gatus or other provider rendering,
- health checks.
release must not define systemd/readiness/monitoring behavior. A release may expose paths or produce resources, but consumers such as daemon, ready, and providers remain separate declarations.
A fuller release declaration may grow into artifact preparation and activation, but it should still compile to plain resources and commands. For OTP release manifests, HostKit provides a narrow built-in ReleaseKit path on otp_release instead of a separate producer entity:
otp_release :my_app,
release_kit: [
cwd: "/opt/apps/my_app",
user: "my-app",
out_dir: "_build/prod/artifacts"
],
manifest: "/opt/apps/my_app/_build/prod/artifacts/my_app.etf"During host_kit.apply, HostKit first collects these ReleaseKit build specs, runs mix release_kit.artifact through the HostKit runner boundary, then reloads the project and consumes the generated manifest normally. Dry runs do not build artifacts. Plain manifest: remains supported for workflows that build artifacts outside HostKit.
A fuller release declaration for downloadable artifacts may look like:
service :gatus do
release :gatus, version: "5.36.0" do
artifact github: "TwiN/gatus",
asset: "gatus-linux-amd64.tar.gz",
checksum: "sha256:..."
unpack strip_components: 1
activate :symlink, atomic: true
keep 3
end
daemon "gatus" do
service(exec_start: path(:opt, "current/gatus/gatus"))
end
endThis future shape should still produce inspectable plan data: directories, files, symlinks, commands, manifests, and cleanup actions. It should not introduce an opaque deployment engine hidden behind the DSL.
When HostKit eventually manages activation, prefer a prepare-then-flip model:
- prepare the new version under an inactive path,
- verify artifact checksum/provenance before activation,
- optionally write a manifest for the prepared version,
- atomically update the active pointer where the target platform supports it,
- leave inactive versions available for rollback until explicit retention cleanup.
Activation should be modeled as plan/apply data. If an atomic symlink swap needs a command, that command should be visible in the plan and should have explicit down-plan behavior.
Rollback remains a down plan, not a release-specific rollback entity.
For future release actions:
- current symlink changes are reversible when the previous target was read into the up plan,
- artifact preparation may be irreversible unless HostKit has enough prior state or a backup/source-bundle strategy,
- retention cleanup should be conservative and must not delete the active version,
- cleanup of old versions should be explicit and inspectable,
- applying a down plan should use the same
HostKit.Applyengine as any other plan.
Traditional deployment tools often include shared/ paths for mutable state across versions. HostKit already has separate primitives for state/config/cache/data roots, storage, env files, and systemd writable paths. A future release API may help describe shared paths, but it must not automatically wire them into runtime behavior. Runtime declarations should still make writable paths and process behavior explicit.
Use release for user-facing declarations. Avoid helper namespaces like release_path/2 unless dogfooding proves they are necessary. Prefer existing generic path conventions such as:
path(:opt, "current/gatus/gatus")If repeated current/version roots become noisy, consider generic convention roots such as :current or :versions rather than release-specific helper names.