• Home
  • LLMs
  • Python
  • Docker
  • Kubernetes
  • Java
  • Maven
  • All
  • About
Docker | Dockerfile
  1. Notes
  2. FROM
  3. ARG
  4. LABEL
  5. ENV
  6. RUN
  7. COPY
  8. CMD
  9. ENTRYPOINT
  10. USER
  11. VOLUME
  12. WORKDIR
  13. EXPOSE

  1. Notes
    Dockerfile contains builds commands that are needed to create a Docker image.

    The Dockerfile supports the following instructions:
    FROM        # create a new build stage from a base image.
    ARG         # use build-time variables.
    LABEL       # add metadata to an image.
    ENV         # set environment variables.
    RUN         # execute build commands.
    COPY        # copy files and directories.
    CMD         # specify default commands.
    ENTRYPOINT  # specify default executable.
    USER        # set user and group id.
    VOLUME      # create volume mounts.
    WORKDIR     # change working directory.
    EXPOSE      # describe which ports your application is listening on.
    ADD         # add local or remote files and directories.
    HEALTHCHECK # check a container's health on startup.
    MAINTAINER  # specify the author of an image.
    ONBUILD     # specify instructions for when the image is used in a build.
    SHELL       # set the default shell of an image.
    STOPSIGNAL  # specify the system call signal for exiting a container.
    See these pages for more information:
    https://docs.docker.com/reference/dockerfile/
    https://docs.docker.com/build/building/best-practices/

    Docker introduced a new backend for building images called Buildkit, which does not expose intermediate build steps in the same way as the legacy builder. To view intermediate build steps and containers using the legacy builder, set the environment variable DOCKER_BUILDKIT=0 before using the docker build command:
    $ DOCKER_BUILDKIT=0 docker build [OPTIONS] PATH | URL | -
    This disables Docker BuildKit, which is now the default builder and offers improved performance, output, and caching. It's recommended to enable BuildKit (DOCKER_BUILDKIT=1) or simply omit the variable.
  2. FROM
    FROM [--platform=<platform>] <image> [AS <name>]
    FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
    FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
    The FROM instruction creates a new build stage from a base image.
    FROM ubuntu:latest
  3. ARG
    ARG <name>[=<default value>] [<name>[=<default value>]...]
    The ARG instruction uses build-time variables (along with their default values).
    ARG image_version="latest"
  4. LABEL
    LABEL <key>=<value> [<key>=<value>...]
    The LABEL instruction adds metadata to an image.
    LABEL IMAGE_NAME="Ubuntu $image_version"
    Note that the label is using a variable name which, in this case, references the argument defined by the instruction ARG.

    Let's use a Dockerfile with the above instructions:
    $ vi Dockerfile
    FROM ubuntu:latest
    
    ARG image_version="latest"
    
    LABEL IMAGE_NAME="Ubuntu $image_version"
    Let's build the Dockerfile:
    $ docker build -t ubuntu-base:latest --build-arg image_version="1.0" .
    You can inspect the created image to check the labels:
    $ docker image inspect ubuntu-base:latest --format '{{json .Config.Labels}}' | jq
    {
      "IMAGE_NAME": "Ubuntu 1.0"
    }
  5. ENV
    ENV <key>=<value> [<key>=<value>...]
    The ENV instruction sets environment variables (also available as regular variables during build-time).
    ENV image_env="dev"
    Let's use a Dockerfile with the above instructions:
    $ vi Dockerfile
    FROM ubuntu:latest
    
    ARG image_version="latest"
    
    ENV image_env="dev"
    
    LABEL IMAGE_NAME="Ubuntu $image_version $image_env"
    Let's build the Dockerfile:
    $ docker build -t ubuntu-base:latest .
    You can inspect the created image to check the labels.
    Note that the environment variable was used to set the label.
    Also note that this time the label used the default value of the argument because it was not set when the image was created.
    $ docker image inspect ubuntu-base:latest --format '{{json .Config.Labels}}' | jq
    {
      "IMAGE_NAME": "Ubuntu latest dev"
    }
    Let's check the env variable within the container:
    $ docker container run --rm -it ubuntu-base:latest /bin/bash -c 'env | grep image_env'
    image_env=dev
  6. RUN
    # Shell form:
    RUN [OPTIONS] <command> ...
    # Exec form:
    RUN [OPTIONS] [ "<command>", ... ]
    The RUN instruction executes build commands.
    RUN apt-get -y update
    You should combine multiple build execution commands into a single instruction whenever possible.
    RUN apt-get -y install curl && apt-get -y install nginx
    You should also avoid updating the operating system or upgrading libraries. If necessary, you should use a base image that you can build only when needed. The base image should also be used, where possible, to install third-party libraries.
  7. COPY
    COPY [OPTIONS] <src> ... <dest>
    COPY [OPTIONS] ["<src>", ... "<dest>"]
    The COPY instruction copies files and directories from the local filesystem into a Docker image.
    COPY ./file1 /tmp
    You can copy files or folders.
    When copying a folder, only its content is copied.

    Let's create few local folders/files:
    $ mkdir folder1
    $ mkdir folder1/folder2
    $ touch folder1/file1
    $ touch folder1/folder2/file2
    Let's use a Dockerfile to copy files/folders into the Docker image:
    $ vi Dockerfile
    FROM ubuntu:latest
    
    RUN mkdir /opt/foo
    RUN mkdir /opt/bar
    
    COPY ./folder1/file1 /opt/foo
    COPY ./folder1 /opt/bar
    Let's build the Dockerfile:
    $ docker build -t ubuntu-copy:latest .
    You can check the copied files/folders
    $ docker container run --rm -it ubuntu-copy:latest /bin/bash -c 'find /opt'
    /opt
    /opt/bar
    /opt/bar/folder2
    /opt/bar/folder2/file2
    /opt/bar/file1
    /opt/foo
    /opt/foo/file1
  8. CMD
    # Exec form:
    CMD ["executable", "parameter1", "parameter2", ...]
    CMD ["parameter1", "parameter2", ...] # when used with ENTRYPOINT
    # Shell form:
    CMD command parameter1 parameter2
    The CMD instruction specifies default commands.

    Let's use a Dockerfile:
    $ vi Dockerfile
    FROM ubuntu:latest
    
    CMD ["bash", "-x"]
    Let's build the Dockerfile:
    $ docker build -t ubuntu-copy:latest .
    You can check the copied files/folders
    $ docker container run --rm -it ubuntu-cmd:latest
    + '[' -z '\s-\v\$ ' ']'
    + shopt -s checkwinsize
    ...
    root@fd2b5bd60f7f:/
  9. ENTRYPOINT
    # Exec form:
    ENTRYPOINT ["executable", "parameter1", "parameter2", ...]
    # Shell form:
    ENTRYPOINT command parameter1 parameter2
    The ENTRYPOINT instruction specifies default executable.

    Let's use a Dockerfile:
    $ vi Dockerfile
    FROM ubuntu:latest
    
    ENTRYPOINT ["bash", "-c"]
    CMD ["top", "-o %CPU", "-H"]
    Let's build the Dockerfile:
    $ docker build -t ubuntu-entrypoint-cmd:latest .
    You can check the copied files/folders
    $ docker container run --rm -it ubuntu-entrypoint-cmd:latest
    top - 09:52:48 up 20:39,  0 user,  load average: 0.60, 0.58, 0.54
    Tasks:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  2.3 us,  2.2 sy,  0.0 ni, 94.6 id,  0.3 wa,  0.0 hi,  0.6 si,  0.0 st
    MiB Mem :  15884.0 total,   9379.6 free,   2099.5 used,   4728.7 buff/cache
    MiB Swap:   4096.0 total,   4096.0 free,      0.0 used.  13784.5 avail Mem
    
      PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
        1 root      20   0    8856   5188   3060 R   0.0   0.0   0:00.01 top
  10. USER
    USER <user>[:<group>]
    USER <UID>[:<GID>]
    The USER instruction sets the user that will be used to run the processes inside the container.

    By default, if the USER instruction is not set, docker container run all processes as root.
    Unless there is a real reason for this, you should avoid running containers using root or privileged users.

    Let's check the mti existing on the Docker host:
    $ id mti
    uid=1002(mti) gid=1002(tek) groups=1002(tek)
    Let's first try a Dockerfile without the USER instruction:
    $ vi Dockerfile-withoutuser
    FROM ubuntu:latest
    Let's build the Dockerfile:
    $ docker build -t ubuntu-withoutuser:latest -f Dockerfile-withoutuser .
    Let's check the user within the container:
    $ docker container run --rm -it ubuntu-withoutuser:latest /bin/bash -c 'id'
    uid=0(root) gid=0(root) groups=0(root)
    Let's try now a Dockerfile with the USER instruction:
    $ vi Dockerfile-withuser
    FROM ubuntu:latest
    
    RUN groupadd -r tek --gid=1002 && useradd -r -g tek --uid=1002 mti
    
    USER mti
    Let's build the Dockerfile:
    $ docker build -t ubuntu-withuser:latest -f Dockerfile-withuser .
    Let's check the user within the container:
    $ docker container run --rm -it ubuntu-withuser:latest /bin/bash -c 'id'
    uid=1002(mti) gid=1002(tek) groups=1002(tek)
    Note that the Dockerfile create the group "tek" with the group id (gid) "1002". It also create the user "mti" with the user id (uid) "1002" and the group id (gid) "1002". The image that we create from this Dockerfile will run with the user "mti".

    Note that the group and user names are, more or less, just friendly names. The important part is the uid and gid. These two elements will map the container's user uid and group gid to the host's user uid and group gid. So, in the above example, the container will be running using the same uid and gid that match the ones of the Docker host. In the host I am using the uid 1002 and gid=1002 are mapped to the user and group "mtitek". This means that the container's user will get the same privileges that the user "mtitek" has.
  11. VOLUME
    VOLUME ["/data"]
    The VOLUME instruction creates volume mounts.

    Let's create a Dockerfile that will use the VOLUME instruction to mount a local directory. I will adapt a bit the nginx Dockerfie to print the logs in a directory.

    Here's the Dockerfile (this is for demo purposes only):
    $ vi Dockerfile-nginx-mounted-log-dir
    FROM nginx:latest
    
    #removing the redirection to stdout/stderr to allow writing logs to directory
    RUN rm -f /var/log/nginx/access.log && rm -f /var/log/nginx/error.log
    
    VOLUME /var/log/nginx
    Let's build the Dockerfile:
    $ docker build -t nginx-mounted-log-dir:latest -f Dockerfile-nginx-mounted-log-dir .
    Let's create a local directroy:
    $ mkdir ./nginx-loc-dir
    Let's run the nginx image (the host's directory ./nginx-loc-dir is mounted to the container's directory /var/log/nginx):
    $ docker container run -d -P -v ./nginx-loc-dir:/var/log/nginx nginx-mounted-log-dir:latest
    0a6ea9efbc1f264287aa2b146d77423fd5d8e9070b7aebd0a4d36d148631c16c
    Check the nginx process:
    $ docker container ls
    CONTAINER ID    IMAGE                          COMMAND                  CREATED         STATUS        PORTS                   NAMES
    8491a320ecd8    nginx-mounted-log-dir:latest   "/docker-entrypoint.…"   8 seconds ago   Up 6 seconds  0.0.0.0:32770->80/tcp   zen_shtern
    Let's access nginx at the published port 32770:
    $ curl http://localhost:32770
    Check the nginx logs:
    $ ls -al ./nginx-loc-dir/
    -rw-r--r-- 1 root   root     91 access.log
    -rw-r--r-- 1 root   root      0 error.log
    $ cat ./nginx-loc-dir/access.log
    172.17.0.1 - - [03/Aug/2020:03:22:16 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
  12. WORKDIR
    WORKDIR /path/to/workdir
    The WORKDIR instruction sets the working directory for the remaining build instructions.
    This working directory will also be the default directory for all processes running inside the container.

    Let's try a Dockerfile with the WORKDIR instruction:
    $ vi Dockerfile-withworkdir
    FROM ubuntu:latest
    
    WORKDIR /tmp
    Let's build the Dockerfile:
    $ docker build -t ubuntu-withworkdir:latest -f Dockerfile-withworkdir .
    Let's check the working directory within the container:
    $ docker container run --rm -it ubuntu-withworkdir:latest /bin/bash -c 'pwd'
    /tmp
  13. EXPOSE
    EXPOSE <port> [<port>/<protocol>...]
    The EXPOSE instruction informs Docker that the container should listen on the specified ports. You still need to manually publish the ports when running the container. Use the -p flag to publish and map ports. Use the -P flag to publish all exposed ports and map them to ephemeral higher-order host ports on the host. By default, ports listen on TCP but you can explicitly set UDP.

    Let's try a Dockerfile with the EXPOSE instruction:
    $ vi Dockerfile-expose
    FROM nginx:latest
    
    EXPOSE 80/tcp
    EXPOSE 80/udp
    Let's build the Dockerfile:
    $ docker build -t nginx-expose:latest -f Dockerfile-expose .
    Let's check the exposed port:
    $ docker container run -d -p 8080:80 nginx-expose:latest
    $ curl -s -o /dev/null -w "%{http_code}" http://localhost:8080
    200
© 2025  mtitek