zot — a lightweight, OCI-native container image registry, deployed as a Nomad service. Anonymous pull by default (overlay-internal), optional authenticated push.
Save as values.hcl, edit, then run:
# The name of the Nomad job.
job_name = "zot"
# The Nomad namespace to deploy into.
namespace = "default"
# The datacenters to deploy to.
datacenters = ["*"]
# The zot container image. zot publishes per-arch images — use zot-linux-amd64 or zot-linux-arm64 to match the target node.
image = "ghcr.io/project-zot/zot-linux-amd64:v2.1.5"
# Host port the registry listens on (the job uses host networking, so it's reachable at <node-ip>:<port>).
port = 5000
# Number of instances (keep at 1 for local-disk storage).
count = 1
# Host directory bind-mounted for the blob store, so images survive restarts. Pin the job (see constraints) to keep this stable.
data_dir = "/opt/zot"
# Placement constraints — e.g. pin to a control-plane node so data_dir stays put. On a nomploy cluster: attribute = "$${meta.nomploy_control_plane}", operator = "=", value = "true".
constraints = []
# Allow anonymous (unauthenticated) pulls. Recommended true for a registry that only listens on a private/overlay network. Only takes effect when htpasswd is set (otherwise the registry is fully open).
anonymous_pull = true
# htpasswd line(s) enabling authenticated push (user:bcrypthash). Generate with: htpasswd -bnBC10 <user> <pass>. Leave empty for a fully open registry (no auth at all).
htpasswd = ""
# The task resources. Raise memory if you enable CVE scanning (Trivy).
resources = {
cpu = 500
memory = 512
}
| Name | Type | Default | Description |
|---|---|---|---|
| job_name | string | "zot" | The name of the Nomad job. |
| namespace | string | "default" | The Nomad namespace to deploy into. |
| datacenters | list | ["*"] | The datacenters to deploy to. |
| image | string | "ghcr.io/project-zot/zot-linux-amd64:v2.1.5" | The zot container image. zot publishes per-arch images — use zot-linux-amd64 or zot-linux-arm64 to match the target node. |
| port | number | 5000 | Host port the registry listens on (the job uses host networking, so it's reachable at <node-ip>:<port>). |
| count | number | 1 | Number of instances (keep at 1 for local-disk storage). |
| data_dir | string | "/opt/zot" | Host directory bind-mounted for the blob store, so images survive restarts. Pin the job (see constraints) to keep this stable. |
| constraints | list | [] | Placement constraints — e.g. pin to a control-plane node so data_dir stays put. On a nomploy cluster: attribute = "$${meta.nomploy_control_plane}", operator = "=", value = "true". |
| anonymous_pull | bool | true | Allow anonymous (unauthenticated) pulls. Recommended true for a registry that only listens on a private/overlay network. Only takes effect when htpasswd is set (otherwise the registry is fully open). |
| htpasswd key | string | "" | htpasswd line(s) enabling authenticated push (user:bcrypthash). Generate with: htpasswd -bnBC10 <user> <pass>. Leave empty for a fully open registry (no auth at all). |
| resources | object | {
cpu = 500
memory = 512
} | The task resources. Raise memory if you enable CVE scanning (Trivy). |
No variables match.
Deploys zot — a lightweight, OCI-native container image
registry — as a Nomad service job. Host-networked, so it's reachable at
<node-ip>:<port>. Storage is local disk (bind-mounted) with dedup, GC and an
untagged-retention policy on by default. Anonymous pull, authenticated push is
the recommended setup for a registry that only listens on a private/overlay network.
# add this registry once
nomad-pack registry add nomploy github.com/Nomploy/nomad-packs
# open (no auth) — fine only on a trusted private network
nomad-pack run zot --registry nomploy
# anonymous pull + authenticated push (recommended)
# generate the hash first: htpasswd -bnBC10 pushuser 'a-strong-pass'
nomad-pack run zot --registry nomploy \
--var 'htpasswd=pushuser:$2y$10$....' \
--var 'anonymous_pull=true'
Pin it to the control plane so the blob store stays on one node, and let nomploy manage pull credentials cluster-wide:
nomad-pack run zot --registry nomploy \
--var 'htpasswd=pushuser:$2y$10$....' \
--var 'constraints=[{attribute="${meta.nomploy_control_plane}",operator="=",value="true"}]'
Then Settings → Registry → Add Registry (URL <node-ip>:5000, prefix apps,
your push user/pass). nomploy publishes the creds to Consul KV and consul-template
renders them onto every node — private multi-node pulls with no creds in job specs.
It serves HTTP, so each node's Docker daemon needs the address in
insecure-registries(a real TLS cert would remove that). This is a per-node daemon setting, not a per-job one.
| Variable | Default | Description |
|---|---|---|
job_name |
zot |
Nomad job name |
namespace |
default |
Nomad namespace |
datacenters |
["*"] |
Datacenters to deploy to |
image |
ghcr.io/project-zot/zot-linux-amd64:v2.1.5 |
zot image (per-arch) |
port |
5000 |
Host port |
count |
1 |
Instances (keep 1 for local storage) |
data_dir |
/opt/zot |
Host dir for the blob store |
constraints |
[] |
Placement constraints (pin for stable storage) |
anonymous_pull |
true |
Allow unauthenticated pulls (needs htpasswd set) |
htpasswd |
"" |
user:bcrypthash for push; empty = fully open |
resources |
{cpu=500, memory=512} |
Task resources |
data_dir requires the Docker task driver's
volumes { enabled = true }.extensions.search.cve to the config and
raise resources.memory (Trivy needs headroom).