This blog post is a crash course to take your docker knowledge, and show how it transfers to Nomad. Once you’ve read through it, keep it as a cheat-sheet!
You may also learn something new about docker while you’re here!
What’s the difference?
Docker allows you to build and deploy containers, using technologies such as chroot, cgroups, and other technologies to isolate userspace processes with a shared kernel on a single server(virtual machine or bare metals).
Nomad allows you to deploy one or more docker containers across one or more nodes in a cluster according to the capacity of the network and availability of the resources.
The tools are complimentary, in this post we’ll explore common docker commands and how they translate into Nomad.
Nomad supports drivers other than Nomad, such as qemu, raw-exec and others, but by far docker is my preferred driver!
docker build, docker push
Well, these two commands aren’t handled within Nomad in any way, typically you’ll use another tool like Github Actions, HCP Waypoint, Jenkins, or others to manage your build process.
docker run
Let’s start by creating and running a docker container!
docker run \
--name service \
-d \
davidlublink/devopsgeneration:pretend-work
How to do the same with Nomad, create a new file called basic-job.nomad with this content:
job "basic-job" {
group "group" {
task "service" {
driver = "docker"
config {
image = "davidlublink/devopsgeneration:pretend-work"
}
}
}
}
Now run this nomad command:
nomad run basic-job.nomad
That’s all there is to it, you can see this nomad job is invoking the ‘docker’ driver here with the image we specified in our docker command.
With the ‘-d’ tag, docker will start the container and disconnect it from the terminal, the normal behavior of Nomad is to run jobs in the background and only report to the operator if the job deployed successfully or not.
docker pull
Usually running ‘docker run’ and letting docker automatically pull the images as needed is good enough for most cases.
However, sometimes you may need to force a pull when updating an image with the same name of an image that exists locally already. (Example using tag ‘latest’ ).
In the Nomad job you can actually specify the configuration ‘force_pull’:
config {
force_pull = true
}
With this command, any time a new allocation is created, Nomad will always check with the registry that is has the latest version of whichever tag you specified.
Environment Variables
Building on the previous example, but let’s add 3 environment variables, here is how you do it with docker:
docker run \
--name service \
-eVAR1=VALUE1 \
-eVAR2=VALUE2 \
-eVAR3=VALUE3 \
-d \
davidlublink/devopsgeneration:pretend-work
In Nomad, we just add a new stanza ‘env’:
job "basic-job" {
group "group" {
task "service" {
driver = "docker"
config {
image = "davidlublink/devopsgeneration:pretend-work"
}
env {
VAR1="VALUE1"
VAR2="VALUE2"
VAR3="VALUE3"
VAR4=<<<VAR
My multi-line value 4!
VAR
}
}
}
nomad run basic-job.nomad
It’s that easy! You can also easily include multi line values using the multi line separation syntax.
Since this article is focused on docker to Nomad, it is out of scope to talk about the template stanza and and how it can be used to set environment variables.
Network -p
docker run -d -p6380:6379 redis
This familiar command is handled in Nomad with the addition of this new block network:
job "basic-job" {
group "group" {
network {
port "redis" {
static = 6380
to = 6379
}
}
task "service" {
driver = "docker"
config {
image = "redis"
ports=["redis"]
}
}
}
}
nomad run basic-job.nomad
Just like that, you have the redis service running and exposed on port 6380 forwarded to port 6379 inside of your container.
In this usage, the ‘static’ field is actually optional, if you exclude it Nomad will chose a random port and assign that instead, this might seem strange but when combined with service discovery it allows packing of containers more densely on servers as it avoids port collisions.
LifeProTip: if you get ‘connection refused’ on a service you deployed, check you remembered to set the ‘ports’ field in the docker config!
docker ps
When you run ‘docker ps’ you get the list of local containers:

In the above output you can see the two Redis containers I spawned earlier in this post.
Here we run the nomad status command and can see our running jobs:
nomad status
ID Type Priority Status Submit Date
basic-job service 50 running 2024-12-16T20:11:10-05:00
second-job service 50 running 2024-12-09T18:48:12-05:00
In this case, we can see my two jobs are running healthy, however unlike the docker output we don’t see the network information, and whether or not the jobs are more complex than a single container.
We can dig a bit further into a specific job like this:
nomad-home job status basic-job
ID = basic-job
Status = running
Summary
Task Group Queued Starting Running Failed Complete
group 0 0 1 0 0
Deployed
Task Group Desired Placed Healthy Unhealthy
group 1 1 1 0
Allocations
ID Node ID Task Group Version Desired Status
ff25ecf2 5969ccc6 group 0 run running
Unfortunately, the blog format has forced me to edit the above output for brevity.
You can see there is an allocation that is deployed and healthy! We know it’s on node 5969ccc6, whatever that is, and the current allocation id is ff25ecf2.
docker exec
This ever so important command leads us always to the eternal question ‘does this image have bash or sh?’. Trick question: sometimes it doesn’t have either!
In docker we first run docker ps to get the CONTAINER ID, with that we can run our exec:
docker exec -it 988fb3fa92ea bash
Here is what is looks like for Nomad, you’ll need to run nomad status and nomad job status [job name] to find that allocation ID.
nomad-home alloc exec ff25ecf2 bash
Which will, like the docker command, give you immediate access:
bash-5.2#
A common variant of the above command appears when dealing with an image that has invoked ‘USER’ to a non-root user and you need root access, in docker exec you add ‘-u root’ before the container id, with nomad, well uh… I guess for now you can’t use the Nomad client for this
In the mean time, if you do need to run as root and have docker daemon access on the Nomad client with your allocation, you can invoke docker directly on your Nomad allocation.
It’s messy, but that’s the only option for now.
docker restart
Here’s the command(container id can be found in docker ps):
docker restart [container id]
Here is the nomad equivalent(alloc id can be found in nomad status [jobname]):
nomad alloc restart [alloc id]
This particular comparison is reasonably straight forward, however as a bonus with Nomad if you ask Nomad to stop an allocation but leave the job running, it’ll stop the given allocation and create a new one(possibly on a new client):
nomad alloc stop [alloc id]
The (dis)advantage of using the ‘stop’ command here is that any changes to the filesystems in the allocation(not covered by persistent volumes) will reset.
docker logs
This important command allows us to access the output of the docker:
docker logs [container id]
Nomad is as easy:
nomad alloc logs [alloc id]
docker stop
Let’s stop a running docker container:
docker stop [container id]
Well, Nomad is just as easy, give it the name of the job and voila:
nomad stop basic-job
docker rm
Let’s delete the container:
docker rm [container id]
In Nomad, it’s a ‘purge’, note that eventually Nomad will run garbage collection and any stopped jobs will be purged.
job stop -purge basic-job
docker login
docker login
Followed by you inevitably copying and pasting credentials into the command line.
In Nomad it is possible to configure Nomad clients with docker credentials, to keep it simple in this post we’ll use the auth stanza:
auth {
username = "myuser"
password = "mypass"
}
Drop the above stanza inside task config stanza, and voila!
Conclusion
Nomad at it’s most basic is just a frontend to deploy your Docker containers, bookmark this article for reference, as it can help you quickly rampup with Nomad skills!
Let me know if I missed anything important in the comments!
Leave a Reply