10. Build

10.1. Images

10.1.1. Build images

$ docker build . -t IMAGE_NAME
$ docker build . -t mycontainer
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM alpine:latest
 ---> 961769676411
Step 2/3 : RUN apk add bash
 ---> Running in 5148740edf53
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
(1/5) Installing ncurses-terminfo-base (6.1_p20190518-r0)
(2/5) Installing ncurses-terminfo (6.1_p20190518-r0)
(3/5) Installing ncurses-libs (6.1_p20190518-r0)
(4/5) Installing readline (8.0.0-r0)
(5/5) Installing bash (5.0.0-r0)
Executing bash-5.0.0-r0.post-install
Executing busybox-1.30.1-r2.trigger
OK: 15 MiB in 19 packages
Removing intermediate container 5148740edf53
 ---> 99ec117fc810
Step 3/3 : CMD /bin/bash
 ---> Running in 6a1b630dbfae
Removing intermediate container 6a1b630dbfae
 ---> ce299736b126
Successfully built ce299736b126
Successfully tagged mycontainer:latest
$ docker build . -t mycontainer:1.0.0
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM alpine:latest
 ---> 961769676411
Step 2/3 : RUN apk add bash
 ---> Running in 5148740edf53
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
(1/5) Installing ncurses-terminfo-base (6.1_p20190518-r0)
(2/5) Installing ncurses-terminfo (6.1_p20190518-r0)
(3/5) Installing ncurses-libs (6.1_p20190518-r0)
(4/5) Installing readline (8.0.0-r0)
(5/5) Installing bash (5.0.0-r0)
Executing bash-5.0.0-r0.post-install
Executing busybox-1.30.1-r2.trigger
OK: 15 MiB in 19 packages
Removing intermediate container 5148740edf53
 ---> 99ec117fc810
Step 3/3 : CMD /bin/bash
 ---> Running in 6a1b630dbfae
Removing intermediate container 6a1b630dbfae
 ---> ce299736b126
Successfully built ce299736b126
Successfully tagged mycontainer:1.0.0
$ docker build . -t mycontainer
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM alpine:latest
 ---> 961769676411
Step 2/3 : RUN apk add bash
 ---> Running in 5148740edf53
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
(1/5) Installing ncurses-terminfo-base (6.1_p20190518-r0)
(2/5) Installing ncurses-terminfo (6.1_p20190518-r0)
(3/5) Installing ncurses-libs (6.1_p20190518-r0)
(4/5) Installing readline (8.0.0-r0)
(5/5) Installing bash (5.0.0-r0)
Executing bash-5.0.0-r0.post-install
Executing busybox-1.30.1-r2.trigger
OK: 15 MiB in 19 packages
Removing intermediate container 5148740edf53
 ---> 99ec117fc810
Step 3/3 : CMD /bin/bash
 ---> Running in 6a1b630dbfae
Removing intermediate container 6a1b630dbfae
 ---> ce299736b126
Successfully built ce299736b126
Successfully tagged mycontainer:latest

10.1.2. List images

$ docker images

10.1.3. Remove images

$ docker rmi IMAGE_NAME_OR_ID

10.2. Build Workflow

  1. Write Dockerfile

    FROM alpine:latest
    RUN apk add bash
    CMD /bin/bash
    
  2. Build image

    $ docker build . -t mycontainer

  3. Run image

    $ docker run -it mycontainer
    
  4. List images

$ docker images

10.3. Dockerfile

10.3.1. FROM

  • The FROM instruction initializes a new build stage and sets the Base Image for subsequent instructions.

FROM alpine
FROM ubuntu          # links to :latest
FROM ubuntu:latest   # always current LTS
FROM ubuntu:rolling  # released every 6 months (also LTS, if it was LTS release)
FROM ubuntu:devel    # released every 6 months (only devel)
FROM python:3.7
FROM python:latest
FROM gcc:8
FROM gcc:9
FROM openjdk:8
FROM openjdk:8-alpine
FROM openjdk:12
FROM openjdk:12-alpine

10.4. Execute shell commands

  • JSON array syntax

10.4.1. RUN

  • Will execute any commands in a new layer on top of the current image and commit the results

  • The resulting committed image will be used for the next step in the Dockerfile

10.4.1.1. Example 1

FROM alpine
RUN /bin/echo 'hello'
$ docker build . -t mycontainer
Sending build context to Docker daemon  4.096kB
Step 1/2 : FROM alpine
 ---> 961769676411
Step 2/2 : RUN /bin/echo 'hello'
 ---> Running in c66d9f7f5f4d
hello
Removing intermediate container c66d9f7f5f4d
 ---> ea39fac384a4
Successfully built ea39fac384a4
Successfully tagged mycontainer:latest

10.4.1.2. Example 2

FROM alpine
RUN apk add bash
$ docker build . -t mycontainer
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM alpine:latest
 ---> 961769676411
Step 2/3 : RUN apk add bash
 ---> Running in 5148740edf53
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
(1/5) Installing ncurses-terminfo-base (6.1_p20190518-r0)
(2/5) Installing ncurses-terminfo (6.1_p20190518-r0)
(3/5) Installing ncurses-libs (6.1_p20190518-r0)
(4/5) Installing readline (8.0.0-r0)
(5/5) Installing bash (5.0.0-r0)
Executing bash-5.0.0-r0.post-install
Executing busybox-1.30.1-r2.trigger
OK: 15 MiB in 19 packages
Removing intermediate container 5148740edf53
 ---> 99ec117fc810
Step 3/3 : CMD /bin/bash
 ---> Running in 6a1b630dbfae
Removing intermediate container 6a1b630dbfae
 ---> ce299736b126
Successfully built ce299736b126
Successfully tagged mycontainer:latest

10.4.2. ENTRYPOINT

  • An ENTRYPOINT helps you to configure a container that you can run as an executable.

FROM alpine
ENTRYPOINT ["/bin/ping"]
$ docker build . -t mycontainer
Sending build context to Docker daemon  4.096kB
Step 1/2 : FROM alpine
 ---> 961769676411
Step 2/2 : ENTRYPOINT ["/bin/ping"]
 ---> Using cache
 ---> 2b4aa9975a77
Successfully built 2b4aa9975a77
Successfully tagged mycontainer:latest

$ docker run -it mycontainer
BusyBox v1.30.1 (2019-06-12 17:51:55 UTC) multi-call binary.

Usage: ping [OPTIONS] HOST

Send ICMP ECHO_REQUEST packets to network hosts

    -4,-6           Force IP or IPv6 name resolution
    -c CNT          Send only CNT pings
    -s SIZE         Send SIZE data bytes in packets (default 56)
    -i SECS         Interval
    -A              Ping as soon as reply is recevied
    -t TTL          Set TTL
    -I IFACE/IP     Source interface or IP address
    -W SEC          Seconds to wait for the first response (default 10)
            (after all -c CNT packets are sent)
    -w SEC          Seconds until ping exits (default:infinite)
            (can exit earlier with -c CNT)
    -q              Quiet, only display output at start
            and when finished
    -p HEXBYTE      Pattern to use for payload

$ docker run -it mycontainer 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: seq=0 ttl=64 time=0.041 ms
64 bytes from 127.0.0.1: seq=1 ttl=64 time=0.094 ms
64 bytes from 127.0.0.1: seq=2 ttl=64 time=0.094 ms
^C
--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.041/0.076/0.094 ms

10.4.3. CMD

  • There can only be one CMD instruction in a Dockerfile

  • If you list more than one CMD then only the last CMD will take effect

  • The main purpose of a CMD is to provide defaults for an executing container

FROM alpine
CMD ["/bin/ping", "127.0.0.1"]
$ docker build . -t mycontainer
Sending build context to Docker daemon  4.096kB
Step 1/2 : FROM alpine
 ---> 961769676411
Step 2/2 : CMD ["/bin/ping", "127.0.0.1"]
 ---> Using cache
 ---> e4992bc1834a
Successfully built e4992bc1834a
Successfully tagged mycontainer:latest

$ docker run mycontainer
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: seq=0 ttl=64 time=0.060 ms
64 bytes from 127.0.0.1: seq=1 ttl=64 time=0.067 ms
64 bytes from 127.0.0.1: seq=2 ttl=64 time=0.124 ms
64 bytes from 127.0.0.1: seq=3 ttl=64 time=0.060 ms
64 bytes from 127.0.0.1: seq=4 ttl=64 time=0.065 ms
^C
--- 127.0.0.1 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max = 0.060/0.075/0.124 ms

10.4.4. CMD vs ENTRYPOINT

  • ENTRYPOINT will pass docker run IMAGE ... arguments to entrypoint

10.4.5. USER

  • Run the rest of the commands as the user

FROM alpine
USER guest
RUN /usr/bin/id
$ docker build . -t mycontainer
Sending build context to Docker daemon  4.096kB
Step 1/3 : FROM alpine
 ---> 961769676411
Step 2/3 : USER guest
 ---> Running in 3861a8f7079f
Removing intermediate container 3861a8f7079f
 ---> 89e29c8da805
Step 3/3 : RUN /usr/bin/id
 ---> Running in c6fcf919ced7
uid=405(guest) gid=100(users)
Removing intermediate container c6fcf919ced7
 ---> a569f8c240ab
Successfully built a569f8c240ab
Successfully tagged mycontainer:latest

$ docker run mycontainer /usr/bin/id
uid=405(guest) gid=100(users)

10.5. Files and directories

10.5.1. COPY

FROM alpine
COPY requirements.txt /data

10.5.2. ADD

  • ADD allows <src> to be a URL

  • If the <src> parameter of ADD is an archive in a recognised compression format, it will be unpacked

FROM alpine
ADD requirements.txt /data

10.5.3. COPY vs ADD

  • Best practices for writing Dockerfile suggests using COPY where the magic of ADD is not required

10.5.4. WORKDIR

  • The WORKDIR instruction sets the working directory for any RUN, CMD, ENTRYPOINT, COPY and ADD instructions that follow it in the Dockerfile

  • Default directory when running container

WORKDIR /data

10.5.5. VOLUME

  • The VOLUME instruction creates a mount point with the specified name and marks it as holding externally mounted volumes from native host or other containers.

VOLUME ["/data"]

10.6. Networking

10.6.1. EXPOSE

  • The EXPOSE instruction does not actually publish the port

  • It functions as a type of documentation between the person who builds the image and the person who runs the container, about which ports are intended to be published

FROM alpine
EXPOSE 80/tcp
EXPOSE 80/udp
EXPOSE 443

10.7. Environmental variables

10.7.1. ENV

ENV <key> <value>
ENV <key>=<value> ...
ENV MY_NAME Jan Twardowski
$ docker build . -t mycontainer
Sending build context to Docker daemon  4.096kB
Step 1/2 : FROM alpine
 ---> 961769676411
Step 2/2 : ENV MY_NAME Jan Twardowski
 ---> Running in f6a7217b8b8a
Removing intermediate container f6a7217b8b8a
 ---> 347cd3b90f0b
Successfully built 347cd3b90f0b
Successfully tagged mycontainer:latest

$ docker run mycontainer env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=4c59a9f37394
MY_NAME=Jan Twardowski
HOME=/root

10.8. Examples

10.8.1. Run Django App in container

FROM python:3.7

COPY . /data
RUN pip install -r /data/requirements.txt
ENV DEBUG false
EXPOSE 8000/tcp

WORKDIR /data
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

10.8.2. Apache 2

FROM debian:stable

RUN apt update && apt install -y --force-yes apache2
EXPOSE 80/tcp
EXPOSE 443/tcp
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]

ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

10.8.3. Django app

## Creating image based on official python image
FROM python:3.7

## Sets dumping log messages directly to stream instead of buffering
ENV PYTHONUNBUFFERED 1

## Install system dependencies
RUN apt update && apt install -y nginx

## Change working directory
WORKDIR /srv

## Creating and putting configurations
COPY habitat /srv/habitat
COPY manage.py /srv/
COPY docker-entrypoint.sh /srv/docker-entrypoint.sh
COPY requirements.txt /srv/requirements.txt
COPY conf/nginx.conf /etc/nginx/sites-enabled/habitatOS

## Installing all python dependencies
RUN echo "daemon off;" >> /etc/nginx/nginx.conf
RUN pip install --no-cache-dir -r /srv/requirements.txt

## Open ports to outside world
EXPOSE 80 80/tcp
EXPOSE 8000 8000/tcp

## When container starts, this script will be executed.
## Note that it is NOT executed during building
CMD sh /srv/docker-entrypoint.sh


## Run like that
# docker build . -t habitatos:latest
# docker run -d --env-file=.env --rm --name habitatOS -p 80:80 habitatos
# docker run -d --env-file=.env --rm --name habitatOS -p 80:80 -v /Users/matt/Developer/habitatOS/habitat:/srv/habitat habitatos
# docker exec -it habitatOS bash

10.9. Docker Hub

  1. Build

    $ docker build -t mycontainer:1.0.0 .
    
  2. Tag

    $ docker tag mycontainer:1.0.0 myusername/mycontainer:latest
    
  3. Publish

    $ docker login
    $ docker push myusername/mycontainer:latest
    
  4. Clean local build

    $ docker image remove mycontainer:1.0.0
    
  5. Run from Hub

    $ docker run myusername/mycontainer
    

10.10. Assignments

10.10.1. Dockerfile

  1. Na bazie czystego Alpine stwórz własny kontener dla PostgreSQL

10.10.2. Create container and run

  1. Ściągnij repozytorium:

  2. Zbuduj projekt

  3. Przygotuj obraz oraz uruchom aplikację wykorzystując Docker

  4. Użyj pliku Dockerfile do opisu środowiska kontenera