Run the container image

Smeltr publishes its image at ghcr.io/smeltr-sh/smeltr, tagged by release. For a cluster, use Kubernetes.

Pull and verify the image

The release workflow signs each image and attaches its package list (a CycloneDX SBOM). A tag can move to a newer image, so pull it, then check and run that exact digest:

docker pull ghcr.io/smeltr-sh/smeltr:<version>
image=$(docker image inspect --format '{{index .RepoDigests 0}}' ghcr.io/smeltr-sh/smeltr:<version>)

Both checks fail unless that workflow, run for a release tag, made the image:

cosign verify "$image" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity-regexp '^https://github.com/smeltr-sh/smeltr/\.github/workflows/release\.yml@refs/tags/v'
cosign verify-attestation --type cyclonedx "$image" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity-regexp '^https://github.com/smeltr-sh/smeltr/\.github/workflows/release\.yml@refs/tags/v'

Name the verified digest the way the commands below expect:

docker tag "$image" smeltr:<version>

Why not latest

A tag names the image it was last pushed to. latest, 1 and 1.2 move to each newer release they cover, which makes them a way to find the newest release. A container keeps the image it was created from, but a new one, such as a worker added on another host or a new Kubernetes pod, may pull whatever the tag names at that moment. After a release those run the new build while the rest stay on the old one.

A worker takes deployments only from a leader of its own major release: 1.2 and 1.3 work together, 2.0 does not. A worker on an older release of that major refuses a configuration that uses something newer, and keeps running what it has. A leader that starts on a newer build upgrades its data without the backup an upgrade begins with. Name the image by its digest, and change the digest only as an upgrade.

Load an image archive

A host that cannot reach ghcr.io loads the image from an archive instead. Compare the archive's SHA-256 checksum with its line in the release's SHA256SUMS, and stop if it is missing or differs. Then load it and check its name, tag and runtime user:

sha256sum smeltr-image.tar.gz
docker load --input smeltr-image.tar.gz
docker image inspect smeltr:<version> --format '{{.Id}} user={{.Config.User}}'

Run the container

The image runs as UID/GID 1000. Prepare persistent host directories before starting it:

sudo install -d -o root -g 1000 -m 0750 /srv/smeltr
sudo install -d -o 1000 -g 1000 -m 0700 /srv/smeltr/leader-data /srv/smeltr/leader-secrets

Save this leader-only configuration as container-smeltr.toml in your current directory. HTTP listens on port 4000 inside the container; the run command below restricts the published port to the host.

[leader]
enabled = true

[leader.http]
bind = "0.0.0.0"
port = 4000
public_origin = "http://127.0.0.1:4000"

[leader.tls]
enabled = false

[leader.storage]
data_dir = "/var/lib/smeltr"

[leader.secrets]
key = "/run/secrets/smeltr/sealing.key"

For a standalone container, append [worker] with enabled = true and group = "default". Its worker state stays within the leader-data mount. Leave the sealing key file absent on a fresh installation.

sudo install -o root -g 1000 -m 0640 ./container-smeltr.toml /srv/smeltr/smeltr.toml
docker run --name smeltr-leader \
  --restart unless-stopped \
  --read-only --cap-drop ALL --security-opt no-new-privileges \
  -p 127.0.0.1:4000:4000 \
  -e SMELTR_CONFIG_FILE=/etc/smeltr/smeltr.toml \
  --mount type=bind,src=/srv/smeltr/smeltr.toml,dst=/etc/smeltr/smeltr.toml,readonly \
  --mount type=bind,src=/srv/smeltr/leader-data,dst=/var/lib/smeltr \
  --mount type=bind,src=/srv/smeltr/leader-secrets,dst=/run/secrets/smeltr \
  smeltr:<version>

Read the first-login credentials from the container output, then follow First login. The bind mounts preserve state when you replace the container. The local smeltr:<version> tag above names the verified image; use the verified digest when deploying on other hosts. Smeltr writes only to those mounts, so the run command makes the root filesystem read-only, drops every Linux capability, and lets no process gain privileges. Use the HTTPS proxy setup before allowing remote browsers or workers to connect. A remote worker cannot reach this loopback-only example.