This the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

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
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
hello