Saturday 12 September 2020

Introduction to Dockerfile

Docker is a layered filesystem so every ADD, COPY and RUN instruction will create a new layer and cache it.


ADD
  • takes in a src and destination
  • lets you copying into the Docker image files/directories from following sources:
    • local file or directory from your host (the machine building the Docker image)
    • you can extract a local tar file from the source directly into the destination
    • URL
  • valid use case for ADD is when you want to extract a local tar file into a specific directory in your Docker image

ARG

  • defines a variable that users can pass at build-time to the builder with the docker build command using the --build-arg <varname>=<value> flag
  • Docker build will always show you the line as is written down in the Dockerfile, despite the variable value - ARG value will not be substituted in the terminal output. [ARG substitution in RUN command not working for Dockerfile]
  • Value of the argument provided in docker build command line will overwrite the (default) one set in Dockerfile.
  • WARNING: all ARG values that are defined before FROM will be reset (empty) after FROM [ARG before FROM in Dockerfile doesn't behave as expected · Issue #34129 · moby/moby]
  • ARG variables are available in build time (RUN, COPY etc...). They are not embedded into image (like ENVs) and therefore can't be used in CMD or ENTRYPOINT commands (like ENVs).

Example:

Dockerfile:


ARG NPM_LOG_LEVEL=warn
RUN npm install --loglevel ${NPM_LOG_LEVEL}


Terminal:

$ docker build --pull --build-arg NPM_LOG_LEVEL=verbose -t my_app_image .


To pass ARG values into container we need to use ENV variables:

ARG APP_NAME=mysqlsh-demo
ARG DOCKER_ENTRYPOINT=docker-entrypoint.sh
...
# ARGs are available only in build time but not runtime so we need to pass their values to ENVs:
ENV APP_NAME=${APP_NAME}
ENV DOCKER_ENTRYPOINT=${DOCKER_ENTRYPOINT}

ENTRYPOINT "/usr/src/${APP_NAME}/${DOCKER_ENTRYPOINT}"


COPY

NOTE for ADD & COPY:
  • All new files and directories are created with a UID and GID of 0, unless the optional --chown flag specifies a given username, groupname, or UID/GID combination to request specific ownership of the content added.
  • Examples:
ADD --chown=someuser:somegroup /foo /bar 
COPY --chown=someuser:somegroup /foo /bar 
Or other combinations of user/group name (or ID); 
--chown=someuser:123 
--chown=anyuser:anygroup 
--chown=1001:1002 
--chown=333:agroupname


CMD
  • Lets you define a default command to run when your container starts
  • Executed in run-time;  does not execute anything at build time
  • Sets default command and/or parameters, which can be overwritten from command line when docker container runs
  • Has three forms:
    • Exec (preferred): CMD ["executable","param1","param2"]
    • ENTRYPOINT default parameters list: CMD ["param1","param2"]
    • ShellCMD command param1 param2
  • Exec form executes stated executable and passes to it params listed.
  • Shell form invokes a command shell (e.g. sh -c) and passes both command (executable) and its params to it.
  • When used in the shell or exec formats, the CMD instruction sets the command to be executed when running the image.
  • If you would like your container to run the same executable every time, then you should consider using ENTRYPOINT in combination with CMD.
  • If the user specifies arguments to docker run then they will override the default specified in CMD.


ENTRYPOINT

  • Has two forms:
    • Exec (preferred): ENTRYPOINT ["executable", "param1", "param2"]
    • ShellENTRYPOINT command param1 param2
  • Exec form executes stated executable and passes to it params listed.
  • Shell form invokes a command shell (e.g. sh -c) and passes both command (executable) and its params to it.
  • Configures a container that will run as an executable. It should be used if container is intended to run the same executable every time. This means that we can pass arguments to the executable set as an entrypoint simply by listing them after the name of the container:          $ docker run ...<container_name> param1 param2...
  • Default values of arguments can be specified with CMD instruction in JSON array format: 
    • CMD ["param1", "param2"]
  • Executed in run-time
  • From Docker best practices:
The best use for ENTRYPOINT is to set the image’s main command, allowing that image to be run as though it was that command (and then use CMD as the default flags). 
Let’s start with an example of an image for the command line tool s3cmd: 
   ENTRYPOINT ["s3cmd"]
   CMD ["--help"] 
Now the image can be run like this to show the command’s help: 
   $ docker run s3cmd 
Or using the right parameters to execute a command: 
   $ docker run s3cmd ls s3://mybucket 
This is useful because the image name can double as a reference to the binary as shown in the command above.
Nice example of entrypoint.sh.

This is an example how can operator (person who is running container from an image) pass arguments to the executable run upon the container's launch:

Dockerfile:

...
ENTRYPOINT [ "/my-app" ]
CMD [ "--param1=arg1_default" ]

Launching the container:

$ docker run ... my-app-image --param1=arg1_value

arg1_value will overwrite param1's default value (arg1_default).

If --param1 is omitted then arg1_default will be applied to param1 which will be passed to my-app executable.

sed - Passing variable from container start to file - Stack Overflow


EXPOSE

Used optionally, only for documenting and giving a hint to whoever runs "docker run" which port should be published (with -p/--publish or -P/--publish-all).

...
EXPOSE 8080
...

This means that when we run the container, we should expose this port to the outside world. We have two options for this:
  • -p, --publish - maps a host port we specify (manually assign) to a running container port
    • docker run -p local_port:container_port
    • Example: docker run -p 8080:8080
  • -P, --publish-all - publishes all exposed ports to ports that Docker randomly picks (available high-order ports, higher than 30000)
    • Example: docker run -P
In both cases a firewall rule is created which maps a container port to a port on the Docker host.

FROM
  • Set the baseImage to use for subsequent instructions
  • must be the first instruction in a Dockerfile.


FROM baseImage
FROM baseImage:tag
FROM baseImage@digest

If we don't want to use Docker Hub as Docker repository but some custom server, we can write:

FROM docker.example.com/image_name


LABEL

Set the name of the image author.
LABEL maintainer="author@example.com"

This info is shown in docker inspect output.


RUN

  • execute commands inside of Docker image
  • these commands get executed once at build time and get written into your Docker image as a new layer
  • often used for installing software packages
  • can be specified in two forms:
    • shell form, when it specifies arguments of /bin/sh -c 
      • e.g. RUN echo "test"
    • exec form, when it specifies an executable and list of its arguments
      • e.g. RUN ["/bin/my_app", "arg1", "arg2"]
  • it can be overridden by command specified withing docker run
Sometimes we need to replace some text in some file with some other. This is how to define default replacement string, how to perform replacement with sed and how to inject replacement string from docker build args:

Dockerfile:

ARG DOCKER_IMAGE_REGISTRY=docker.example.com
FROM ${DOCKER_IMAGE_REGISTRY}/golang:alpine as build
# If we add
#    RUN cat /etc/apk/repositories
# this will show that default apk package repositories are HTTP ones:
#    http://dl-cdn.alpinelinux.org/alpine/v3.10/main
#    http://dl-cdn.alpinelinux.org/alpine/v3.10/community
# We need to use some which supports HTTPS which can be found here:
#    https://github.com/alpinelinux/aports/blob/master/main/alpine-mirrors/mirrors.yaml
# Example:
#    If we set https://mirror.fit.cvut.cz/alpine/ via
#       RUN sed -i 's/http\:\/\/dl-cdn.alpinelinux.org/https\:\/\/mirror.fit.cvut.cz/g' /etc/apk/repositories
#    then registry will be:
#       https://mirror.fit.cvut.cz/alpine/v3.10/main
#       https://mirror.fit.cvut.cz/alpine/v3.10/community
# Use --build-arg in docker build to pass custom APK registry host. Example:
#    $ docker build --build-arg APK_PACKAGE_REGISTRY=mirror.fit.cvut.cz --no-cache --network host -t my-app .
ARG APK_PACKAGE_REGISTRY=apk.example.com/random/path/alpine-remote
RUN sed -i "s:http\:\/\/dl-cdn.alpinelinux.org:https\:\/\/${APK_PACKAGE_REGISTRY}:g" /etc/apk/repositories
RUN cat /etc/apk/repositories
RUN apk --no-cache add ca-certificates

# alpine contains /bin/sh so we can use it via
#    $ docker exec -it my-app sh
FROM ${DOCKER_IMAGE_REGISTRY}/alpine
LABEL maintainer="bojan.komazec@example.com"
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY ./bin/my-app /my-app
ENTRYPOINT [ "/my-app" ]
CMD [ "--param0=arg0" ]

To build this image and override APK_PACKAGE_REGISTRY:

$ docker build --build-arg APK_PACKAGE_REGISTRY=mirror.fit.cvut.cz --no-cache --network host -t my-app-image .

bash - How do I use variables in a sed command? - Ask Ubuntu
How to assign variable and use sed to replace contents of configuration file in Dockerfile? - Unix & Linux Stack Exchange


USER

  • Set the user name or UID to use when running the image in addition to any subsequent CMD, ENTRYPOINT, or RUN instructions that follow it in the Dockerfile.
  • sets the current user
  • this user is ignored by ADD and COPY commands (you need to use --chown)
  • if your service/app can run as non-root, run it as non-root
  • node Docker images from DockerHub contain by default pre-created non-root user node and group node for this purpose

RUN groupadd -g 999 appuser && \
useradd -r -u 999 -g appuser appuser
USER appuser


WORKDIR


Towards unprivileged container builds

[Docker RUN vs CMD vs ENTRYPOINT by Yury Pitsishin]
What is the difference between CMD and ENTRYPOINT in a Dockerfile?

The ENTRYPOINT specifies a command that will always be executed when the container starts.
The CMD specifies arguments that will be fed to the ENTRYPOINT.

Dockerfile cheat sheet (kapeli.com)
Dockerfile: ENTRYPOINT vs CMD

No comments: