Time
Navigate to the basics/time
directory in the examples repo.
Take a look at the get-time.sh
bash script.
#!/bin/bash
# Ref: See section on Daytime Protocol
# https://www.nist.gov/pml/time-and-frequency-division/time-distribution/internet-time-service-its
# first arg is frequency, default is 5 seconds
freq=${1:-5}
echo "Fetch UTC(NIST) time every ${freq} seconds..."
while true; do
if dt=$(cat </dev/tcp/time.nist.gov/13 | tail -n 1); then
if [[ "$dt" =~ .*"UTC(NIST)".* ]]; then
d=$(echo "$dt" | cut -d " " -f 2)
t=$(echo "$dt" | cut -d " " -f 3)
echo "$d $t"
fi
fi
sleep "${freq}"
done
Using a loop, this program reads a TCP socket on a Linux-based system for
fetching the current time from the NIST Internet time
service
and prints a formatted version of the response to the terminal.
The program accepts an argument specifying the frequency for fetching the time
in seconds, defaulting to 5 if not provided.
You can test the program on your own machine.
chmod +x get-time.sh
./get-time.sh 1
Fetch UTC(NIST) time every 1 seconds...
21-09-02 18:15:02
21-09-02 18:15:03
21-09-02 18:15:04
21-09-02 18:15:12
^C
😲 Note
If this doesn’t work on your machine, don’t worry – you’ve
just experienced one of the big reasons why containers are so useful!
Continue to the next page to containerize the time app so we can run it in a
standard Linux environment that will work everywhere Docker is supported.
1 - Dockerfile with CMD
In the Dockerfile for the previous example, you used the ENTRYPOINT
instruction to specify the process to execute in the container. This time you’ll
use the CMD
instruction instead and then explore the differences between the
two.
FROM debian
COPY get-time.sh /usr/local/bin
RUN chmod +x /usr/local/bin/get-time.sh
CMD [ "/usr/local/bin/get-time.sh" ]
Build a Docker image to run this.
docker build -f Dockerfile.using_cmd -t get-time .
Since Docker looks for Dockerfile
by default and you’re going to try using a
few different Dockerfiles, you need to use the -f
option to be explicit about
which one to use for the build.
Now print the time using a container.
docker run -it --rm --name timectr get-time
Fetch UTC(NIST) time every 5 seconds...
21-09-02 19:28:45
21-09-02 19:28:51
We used a new option for the docker run
command: -it
. This is actually a
combined option using two short flags, -i
and -t
. The first allows us to
pass input to the container over stdin
; the second attaches a pseudo-terminal,
which means for one thing it can respond to the SIGINT
signal when you press
Ctrl-C
on your keyboard to terminate the process.
The container appears to behave the same way as when we used ENTRYPOINT
before. However, one difference is that CMD
acts more like a default. You
can easily override the default command, as shown below.
docker run -it --rm --name timectr get-time echo hello
The get-time.sh
script is no longer executed; instead, the command echo
with
the argument hello
was executed in the container. Since the base image for the
get-time
image was debian
, the standard echo
command exists in the path
when the container is launched.
Because the argument that you provide in the docker run
command replaces the
default command, you can’t just provide the frequency option like this (since
there is no 2
command).
# this won't work!
docker run -it --rm --name timectr get-time 2
Instead, you need to supply the entire command to override the default one:
docker run -it --rm --name timectr get-time get-time.sh 2
Fetch UTC(NIST) time every 2 seconds...
21-09-02 19:29:42
21-09-02 19:29:44
^C
Since /usr/local/bin
is in the path for the debian
image, it wasn’t
necessary to fully qualify the path for get-time.sh
. The fully-qualified
path was used in the CMD
instruction is often a good practice for clarity.
Let’s revisit using ENTRYPOINT
next.
2 - Dockerfile with ENTRYPOINT
Let’s rebuild the get-time
image using an ENTRYPOINT
.
FROM debian
COPY get-time.sh /usr/local/bin
RUN chmod +x /usr/local/bin/get-time.sh
ENTRYPOINT [ "/usr/local/bin/get-time.sh" ]
Rebuild the Docker image.
docker build -f Dockerfile.using_entrypoint -t get-time .
Now run a container.
docker run -it --rm --name timectr get-time
Fetch UTC(NIST) time every 5 seconds...
21-09-02 19:30:53
^C
So far the behavior seems the same. However, unlike with CMD
, arguments
don’t override the ENTRYPOINT
; instead, they are passed to. This behavior is
more aligned with our expectations for containers that run specific programs.
docker run -it --rm --name timectr get-time 2
Fetch UTC(NIST) time every 2 seconds...
21-09-02 19:33:26
21-09-02 19:33:28
^C
3 - Dockerfile with both
What if we want to override the container to behave the way it does with an
ENTRYPOINT
, but we want to override the default frequency of 5
seconds?
You use both ENTRYPOINT
and CMD
.
FROM debian
COPY get-time.sh /usr/local/bin
RUN chmod +x /usr/local/bin/get-time.sh
ENTRYPOINT [ "/usr/local/bin/get-time.sh" ]
CMD [ "2" ]
Rebuild the Docker image.
docker build -f Dockerfile.using_entrypoint -t get-time .
Run a container.
docker run -it --rm --name timectr get-time
Fetch UTC(NIST) time every 2 seconds...
21-09-02 19:30:53
^C
What happens here is that the value for CMD
acts as a default argument that
will be passed to the command specified by ENTRYPOINT
. You can still pass an
argument when you create the container and it will just override CMD
’s default
value.
docker run -it --rm --name timectr get-time 3
Fetch UTC(NIST) time every 3 seconds...
4 - Summary
The time
example introduced you to the difference between ENTRYPOINT
and CMD
, and how you can use both together.
In summary, use CMD
when you might want to run other processes in the
container. Some containers are specifically designed to make multiple tool
commands available, so using CMD
is convenient.
Use ENTRYPOINT
when you want a container to behave like a specific tool and
you want to easily supply arguments without inadvertently overriding the default
command.
Use ENTRYPOINT
and CMD
together when you want the behavior of using
ENTRYPOINT
, but want to specify potentially more appropriate default
arguments using CMD
that will override the containerized program’s defaults.
🤯
The nice thing about this combination is that you can still override
the CMD
defaults (which override the program’s defaults)
when you launch a container!
For options that should not be overridden, they should be specified as part of
the ENTRYPOINT
instruction. Any options set using CMD
or when running a
container will be appended to the ENTRYPOINT
options.
Overriding the ENTRYPOINT when running a container
Finally, as with CMD
, you can even override the ENTRYPOINT
when you run a
container. It just takes requires a little more effort on the command line.
You use the --entrypoint
option to specify the command (ex, echo
), and then
supply an argument after the image name (ex, hello
) as usual.
docker run -it --rm --name timectr --entrypoint echo get-time hello