Step 1: Create a Basic Dockerfile

Objective: Write a basic Dockerfile to build a simple image to print the date.

Please view the references in Dockerfile Elements at the beginning of the lab when needed.

5.1.1. Create a test image

Our first task is to create a test image. Writing a Dockerfile is just like running commands in the terminal on your VPS, except there is no user interaction.

We need to decide on the OS that our Docker image will use. Let’s use Alpine Linux because it is small and lightweight. Also, it works well for Python projects.

  1. To get started, we need to do is create our project environment.

    • We’ll create our project directory in our home folder and then create an empty file using touch.

    mkdir ~/alpine-demo
    cd ~/alpine-demo
    touch Dockerfile
    
  2. Add contents to Dockerfile.

    • The first line is a comment that describes the file: # A simple Dockerfile using Alpine

    • The second line defines the image, which uses the latest Alpine image: FROM alpine:latest

    • The next command updates the list of packages (application) and then installs some essential tools.

      • We can connect commands using &&.

      • Using \ continues the command on the next line.

      • When assembled, it becomes: RUN apk update && apk add nano && apk add curl

    File contents of Dockerfile
    1# A simple Dockerfile using Alpine
    2
    3FROM alpine:latest
    4
    5RUN apk update && \
    6    apk add nano curl
    
  3. Let’s build an image to verify that there are no errors.

    We’ll use docker build to execute command docker build -t alpine-demo:test .

    docker build

    The build command.

    -t

    Specify an image name. Otherwise, the image has the label of <none>.

    alpine-demo:test

    Name (alpine-demo) and tag (test) pair.

    .

    Use the Dockerfile in the current directory.

    Docker build command
     docker build -t alpine-demo:test .
    
    Output
    root@vps298933:~/alpine-demo# docker build -t alpine-demo:test .
    Sending build context to Docker daemon  2.048kB
    Step 1/2 : FROM alpine:latest
     ---> 5cb3aa00f899
    Step 2/2 : RUN apk update &&     apk add nano &&     apk add curl
     ---> Running in 0c19b05d0899
    fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz
    fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz
    v3.9.3-11-g3703250ef2 [http://dl-cdn.alpinelinux.org/alpine/v3.9/main]
    v3.9.3-10-g9333b6b69d [http://dl-cdn.alpinelinux.org/alpine/v3.9/community]
    OK: 9758 distinct packages available
    (1/5) Installing libmagic (5.35-r0)
    (2/5) Installing ncurses-terminfo-base (6.1_p20190105-r0)
    (3/5) Installing ncurses-terminfo (6.1_p20190105-r0)
    (4/5) Installing ncurses-libs (6.1_p20190105-r0)
    (5/5) Installing nano (3.2-r0)
    Executing busybox-1.29.3-r10.trigger
    OK: 19 MiB in 19 packages
    (1/5) Installing ca-certificates (20190108-r0)
    (2/5) Installing nghttp2-libs (1.35.1-r0)
    (3/5) Installing libssh2 (1.8.2-r0)
    (4/5) Installing libcurl (7.64.0-r1)
    (5/5) Installing curl (7.64.0-r1)
    Executing busybox-1.29.3-r10.trigger
    Executing ca-certificates-20190108-r0.trigger
    OK: 20 MiB in 24 packages
    Removing intermediate container 0c19b05d0899
     ---> bffd4113050a
    Successfully built bffd4113050a
    Successfully tagged alpine-demo:test
    root@vps298933:~/alpine-demo#
    
  4. Let’s verify that the image exists by evaluating the images loaded on our VPS.

    docker images
    

    Successful Build

    You should see the image called alpine-demo with tag test.

    Successful Build
    root@vps298933:~/alpine-demo# docker images
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    alpine-demo         test                363f32a58198        5 seconds ago       16.8MB
    

    Failed Build

    If something went wrong, just remove the broken or unnamed image. You must specify the ID of the image. docker rmi IMAGE_ID

    Successful Build
    root@vps298933:~/alpine-demo# docker images
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    <none>              <none>              2f5e973de625        6 seconds ago       16.8MB
    
    Remove an image by ID
    docker rmi 2f5e973de625
    

    Note

    You cannot remove an image if there is an active container. You must first remove the running container.

    Remove stopped container first
    root@vps298933:~/alpine-demo# docker images
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    <none>              <none>              2f5e973de625        6 seconds ago       16.8MB
    alpine              latest              caf27325b298        2 weeks ago         5.53MB
    root@vps298933:~/alpine-demo# docker rmi 2f5e973de625
    Error response from daemon: conflict: unable to delete 2f5e973de625 (must be forced) - image is being used by stopped container a584c86d7633
    root@vps298933:~/alpine-demo# docker rm a584c86d7633
    a584c86d7633
    root@vps298933:~/alpine-demo# docker rmi 2f5e973de625
    Deleted: sha256:2f5e973de62572bd6bf30b754e8587946cf04a6b8a0b018072394f657ac13b41
    Deleted: sha256:8e3ff38af03f215e84c6db1bc05c9a12166a01367e7fac59251a41f8c183dd53
    root@vps298933:~/alpine-demo# docker images
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    alpine              latest              caf27325b298        2 weeks ago         5.53MB
    nextcloud           latest              7f69ccc610f0        3 weeks ago         569MB
    root@vps298933:~/alpine-demo#
    

5.1.2. Adding Functionality

So far, you built an image using Alpine Linux. You then added two packages using the apk command. Now, let’s make the image do something!

Our image wil print the date when it runs as a container!

  1. Adding functionality to the image.

    1. Edit the Dockerfile

    2. Then, add a command that displays the date: CMD ["date"]

    3. Rebuild the image: docker build -t alpine-demo:test .

    4. Verify the image is the list using the name and tag: docker images

    File contents of Dockerfile
    1# A simple Dockerfile using Alpine
    2
    3FROM alpine
    4
    5RUN apk update && \
    6    apk add nano curl
    7
    8# Print the date
    9CMD ["date"]
    
  2. Now, let’s create a container to run the image and see what date it displays!

    The command uses this format: docker run <image>:<tag>

    • docker run creates a new container and runs a command.

    • alpine-demo:test: The specific image used to create a container

    docker run alpine-demo:test
    
    Output
    root@vps298933:~/alpine-demo# docker run alpine-demo:test
    Thu Feb 14 12:31:15 UTC 2019
    

    Docker created a new container from image alpine-demo:test, ran the date command, and then stopped the container. The container still exists, but it not active.

    docker ps -a
    
    Output
    CONTAINER ID        IMAGE               COMMAND       CREATED             STATUS                         PORTS       NAMES
    deeccc847527        alpine-demo:test    "date"        About an hour ago   Exited (0) About an hour ago               suspicious_lamarr
    
  3. Let’s run it again but with terminal access using the sh terminal program.

    We’ll also use the --rm flag to remove the container once it stops.

    • -i flag runs the container in interactive mode that can accept user input.

    • -t flag creates a terminal inside of the container.

    • --rm flag automatically removes the container when the process exits.

    • /bin/sh starts the container running the sh terminal.

    Tip

    You can combine flags!

    docker run -i -t --rm alpine-demo:test /bin/sh
    

    Is the same as:

    docker run -it --rm alpine-demo:test /bin/sh
    
    A shell prompt in the container
    root@vps298933:~/alpine-demo# docker run -it alpine-demo:test /bin/sh
    / #
    
  4. We’ve entered the container and we have a command prompt as root. Let’s run a few commands.

    Commands
    whoami               # Displays the logged in use
    date                 # Displays the current date and time
    cat /etc/*-release   # Displays information about the OS
    ifconfig             # Displays the IP address
    exit                 # exits the container shell
    
    Entering a container shell
    root@vps298933:~/alpine-demo# docker run -it alpine-demo:test /bin/sh
    / # whoami
    root
    / # date
    Thu Feb 14 13:39:40 UTC 2019
    / # ifconfig
    eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02
              inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
              RX packets:11 errors:0 dropped:0 overruns:0 frame:0
              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:0
              RX bytes:866 (866.0 B)  TX bytes:0 (0.0 B)
    
    lo        Link encap:Local Loopback
              inet addr:127.0.0.1  Mask:255.0.0.0
              UP LOOPBACK RUNNING  MTU:65536  Metric:1
              RX packets:0 errors:0 dropped:0 overruns:0 frame:0
              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000
              RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
    
    / # exit
    root@vps298933:~/alpine-demo#
    
  5. Run docker ps -a again.

    You might have a containers that have sopped. We do not need them running. So let’s use docker rm <CONTAINER ID> to remove any unused container.

    Output
    root@vps298933:~/alpine-demo# docker ps -a
    CONTAINER ID        IMAGE               COMMAND        CREATED             STATUS                         PORTS       NAMES
    deeccc847527        alpine-demo:test    "date"         About an hour ago   Exited (0) About an hour ago               suspicious_lamarr
    root@vps298933:~/alpine-demo# docker rm deeccc847527
    deeccc847527
    root@vps298933:~/alpine-demo# docker ps -a
    CONTAINER ID        IMAGE               COMMAND        CREATED             STATUS              PORTS                  NAMES
    root@vps298933:~/alpine-demo#
    

Tip

Use docker system prune to clean up Docker easily by removing stopped containers and data from failed builds.

docker system prune
 root@test2:~/alpine-demo# docker system prune
 WARNING! This will remove:
   - all stopped containers
   - all networks not used by at least one container
   - all dangling images
   - all dangling build cache

 Are you sure you want to continue? [y/N] y
 Deleted Containers:
 8a0a3e102c2830cf8aed8e7afd9b75d425ff90ce5e61f8f2e6c96fab1b67a5e7
 6f1770ac684ae179c5f5453df53c8723967658722cbf790173cdf436c744f2eb

 Total reclaimed space: 0B
 root@test2:~/alpine-demo#

5.1.4. Container Names

Docker assigns a random ID and name to each container for uniqueness. We can use the container ID or container name for most operations. Names are easier to work with. We can use the --name flag to assign a name.

  1. Start the container using the --name flag.

    docker run --name alpine-demo alpine-demo:test
    
    Output
    root@vps298933:~# docker run --name alpine-demo alpine-demo:test
    Fri Feb 15 02:42:29 UTC 2019
    root@vps298933:~#
    root@vps298933:~# docker ps -a
    CONTAINER ID        IMAGE               COMMAND         CREATED             STATUS                     PORTS            NAMES
    13a9b40001c4        alpine-demo:test    "date"          5 seconds ago       Exited (0) 5 seconds ago                    alpine-demo
    
  2. Because container names are IDs are unique, Docker will not create another container using the same name. But, let’s try!

    • Press the up arrow ↑ to display the previous command.

    docker run --name alpine-demo -it alpine-demo:test
    
    Output with an error message
    root@vps298933:~# docker run --name alpine-demo -it alpine-demo:test
    docker: Error response from daemon: Conflict. The container name "/alpine-demo" is already in use by container "13a9b40001c4d7ee2bf4e4439a5763e4721e978bca143709677b3c4d81772ec3". You have to remove (or rename) that container to be able to reuse that name.
    See 'docker run --help'.
    
  3. We can remove the container easily by name.

    • Also, the Tab key works to auto-complete the name of the container

    docker rm alpine-demo
    
    Output
    root@vps298933:~# docker rm alpine-demo
    alpine-demo
    root@vps298933:~# docker ps -a
    CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
    

5.1.5. Wrap-up

We demonstrated how to create a Docker image, create a container to run an instance of the image, and then remove the stopped container.

  1. We learned how to create a docker image from commands in a Dockerfile using the docker build command: docker build -t name:tag .

    • -t flag specifies an image name. Otherwise, the image has the label of <none>

    • name:tag specifies the name and tag of the image.

    • . use the Dockerfile in the current directory.

  2. We learned more about the docker run command: docker run --name some_name -i -t --rm image:tag [COMMAND]

    • --name [container-name] flag assigns that name to the container.

    • -i flag runs the container in interactive mode that can accept user input.

    • -t flag creates a terminal inside of the container.

    • --rm flag automatically removes the container when the process exits.

    • image:tag is the specific image used to create a container.

    • /bin/sh command starts the container running the sh terminal.

Next, we’ll learn how to keep an image running in daemon mode, which means that the processes run in the background and do not exit when the task completes.