# Tools : Docker : Dockerfiles ## Overview - Instructions are case-insensitive, but all caps by convention. - Comments must start at the beginning of the line. - Leading whitespace (before either) is ignored but discouraged. - Dockerfile must begin with FROM. (This may be after parser directives, comments, and ARGs.) #### Best Practices - Use multi-stage builds. - Leverage build cache. - Exclude w/.dockerignore (similar patterns as .gitignore). - Pin base image versions to specific digests: `FROM alpine:3.19@sha256:13b7e62e8df80264dbb747995705a986aa530415763a6c58f84a3ca8af9a5bcd` #### Multi-Stage Builds - Multi-stage builds let you reduce the size of your final image, by creating a cleaner separation between the building of your image and the final output. - Split your Dockerfile instructions into distinct stages to make sure that the resulting output only contains the files that's needed to run the application. - Using multiple stages can also let you build more efficiently by executing build steps in parallel. ## Parser Directives - Directives must come before any other lines, including empty ones. - Directives are case-insensitive, but lowercase by convention. - Each directive may only be used once. - The only directives are "syntax" and "escape", and neither are particularly important. ## ENV Vars - Variables declared with `ENV` can be used in instructions with `$var` or `${var}`. - The brace syntax is typically used to facilitate variable names with no whitespace: `${foo}_bar` - Also supports a few standard bash modifiers: ``` ${var:-word} if `var` is NOT set, use `word` instead ${var:+word} if `var` is set, use `word`, else empty string ``` ## Commands — CMD, ENTRYPOINT, RUN #### Shell vs Exec Forms ``` Exec : INSTRUCTION [ "executable", "param1", "param2" ] (JSON array) (no wildcards or env vars) Shell : INSTRUCTION command param1 param2 ``` - To use Exec Form with a shell: `[ "sh", "-c", "echo $HOME" ]` - To use Shell Form but still receive signals: `exec <command> ...` - Full path not necessary if executable is found in `PATH`. #### Exec Form The Exec Form is best used to specify an `ENTRYPOINT` instruction, combined with `CMD` for setting default arguments that can be overridden at runtime. #### Shell Form The Shell Form is basically a string, and thus supports escaping newlines to span lines: ```Dockerfile RUN source $HOME/.bashrc && \ echo $HOME ``` Also supports heredocs: ```Dockerfile RUN <<EOF source $HOME/.bashrc && \ echo $HOME EOF ``` #### CMD & ENTRYPOINT - Should specify at least one of CMD or ENTRYPOINT commands. - Each should appear only once. (If multiple times, only last is used.) **CMD w/wo ENTRYPOINT — Exec Form** The purpose of `CMD` is to provide defaults for an executing container. These defaults can include an executable, or they can omit the executable, in which case you must also specify an `ENTRYPOINT` instruction. If `CMD` is used to provide default arguments for `ENTRYPOINT`, use Exec Form for both. Arguments passed to `docker run` will override `CMD` and be appended after all elements in an Exec Form `ENTRYPOINT`. **ENTRYPOINT — Shell Form** The Shell Form of `ENTRYPOINT` prevents `CMD` or CLI args from being used. It also starts your `ENTRYPOINT` as a subcommand of `/bin/sh -c`, which does not pass signals — including `SIGTERM` from `docker stop`. If you need to write a starter script for a single executable, you can ensure that the final executable receives Unix signals by using exec and gosu commands. ```bash #!/usr/bin/env bash set -e ... exec "$@" ``` **Conclusion** ``` No ENTRYPOINT ENTRYPOINT bar p2 ENTRYPOINT [ "bar", "p2" ] ┌────────────────── ───────────────── ────────────────────────── No CMD │ error /bin/sh -c bar p2 bar p2 CMD [ "foo", "p1" ] │ foo p1 /bin/sh -c bar p2 bar p2 foo p1 CMD foo p1 │ /bin/sh -c foo p1 /bin/sh -c bar p2 bar p2 /bin/sh -c foo p1 ``` If you would like to run the same executable every time, use `ENTRYPOINT` + `CMD`. Otherwise, just use `CMD`. > [!NOTE] > If `CMD` is defined from the base image, setting `ENTRYPOINT` will reset `CMD` to an empty value. In this scenario, `CMD` must be defined in the current image to have a value. ## Instructions ```Dockerfile +e ADD Copy files into image. (Gen1 - remote sources and autoextraction) ARG Use build-time variables. (Seems weird and unnecessary.) CMD Specify defaults for container execution. +e COPY Copy files into image. (Gen2 - only actual copying, but also from other images) ENTRYPOINT Specify default executable. e ENV Set environment variables. e EXPOSE Describe which ports your application is listening on. +e FROM Create a new build stage from a base image. HEALTHCHECK Check a container's health on startup. e LABEL Add metadata to an image. MAINTAINER Specify the author of an image. (DEPRECATED — use LABEL instead) e ONBUILD Specify instructions for when the image is used in a build. + RUN Execute build commands. SHELL Set the default shell of an image. e STOPSIGNAL Specify the system call signal for exiting a container. e USER Set user and group ID. e VOLUME Create volume mounts. (Seems weird and unnecessary.) e WORKDIR Change working directory. -- ----------- ----------------------------------------------------------- + adds a layer (otherwise only affects metadata) e supports env var substitution You can also use env vars with CMD/ENTRYPOINT/RUN (in shell form), but that's actually handled by the command shell, not the builder. ``` ## Reference #### ADD ```Dockerfile ADD [OPTIONS] <src> ... <dest> ADD [OPTIONS] [ "<src>", ... "<dest>" ] # this form required for paths containing whitespace Options: --checksum --chmod see: COPY --chown see: COPY --keep-git-dir ``` - Supports remote sources, and when specifying a compressed file, will auto-extract it. - First version. The `COPY` instruction was added later to only do normal copying from local sources to avoid unpleasant surprises. #### CMD ```Dockerfile CMD [ "executable","param1","param2" ] # Exec Form CMD [ "param1","param2" ] # Exec Form as default parameters to ENTRYPOINT CMD command param1 param2 # Shell Form ``` #### COPY ```Dockerfile COPY [OPTIONS] <src1> ... <dest> COPY [OPTIONS] [ "<src1>", ... "<dest>" ] # this form required for paths containing whitespace Options: --chmod=<perms> --chown=<user>[:<group>] # UID/GID or names (looked up in /etc/passwd and /etc/group) # defaults to 0 --from=<image|stage|context> # look for <src> in specified image (relative to root) ``` - Preferred over `ADD` unless you need to copy from a remote source or extract from a compressed file. - Local (host) sources are relative to build context and support wildcards via Go's `filepath.Match` rules. - Destinations are absolute or relative to `WORKDIR`. - Destination directories are auto-created. - If multiple sources, destination must be a directory and end in "/". #### ENTRYPOINT ```Dockerfile ENTRYPOINT [ "executable", "param1", "param2" ] # Exec Form (preferred) ENTRYPOINT command param1 param2 # Shell Form ``` #### ENV ```Dockerfile ENV k1=v1 ... ``` #### EXPOSE ```Dockerfile EXPOSE <port> [<port>/<protocol>...] ``` - Doesn't actually publish the port. - 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. With `docker run`, - Use `-p` to publish and map one or more ports. - Use `-P` to publish all exposed ports and map them to high-order ports. - Defaults to TCP unless you specify otherwise: 80/udp - To expose a port on both protocols, specify each separately. #### FROM ```Dockerfile FROM <image> [AS <name>] <image> = <repo>:<tag>[@sha256:<digest>] ex: alpine:3.19@sha256:13b7e62e8df80264dbb747995705a986aa530415763a6c58f84a3ca8af9a5bcd ``` - Initializes a new build stage and sets the base image for subsequent instructions. - If image spec has no tag or digest, assumes tag "latest". - If name given, can be used in subsequent instructions to refer to the image: ``` FROM <name> COPY --form=<name> RUN --mount=type=bind,from=<name> ``` - Can appear multiple times to create multiple images or use one stage as a dependency for another. #### HEALTHCHECK TODO #### LABEL ```Dockerfile LABEL k1=v1, ... ``` #### RUN ```Dockerfile RUN [OPTIONS] [ "<command>", ... ] # Exec Form RUN [OPTIONS] <command> ... # Shell Form Options: --mount create filesystem mounts that the build can access --network which networking environment the command is run in ``` > [!NOTE] > Always combine `apt-get update` with `apt-get install -y` in the same `RUN` statement. #### SHELL ```Dockerfile SHELL [ "executable", "parameters" ] ``` Must use JSON form. Defaults to: `[ "/bin/sh", "-c" ]` #### STOPSIGNAL ```Dockerfile STOPSIGNAL <signal> ``` - Sets the system call signal that will be sent to the container to exit. - This signal can be a signal name in the format `SIG<NAME>`, for instance `SIGKILL`, or an unsigned number that matches a position in the kernel's syscall table, for instance 9. - The default is `SIGTERM` if not defined. #### USER ```Dockerfile USER <user>[:<group>] ``` - Determines user/group for `CMD`, `ENTRYPOINT`, and `RUN` commands. - Can use UID/GID or names (looked up in `/etc/passwd` and `/etc/group`). - If group specified, user will _only_ have that group. Otherwise, defaults to "root". #### WORKDIR ```Dockerfile WORKDIR <path> ``` - Sets basedir for subsequent `ADD`, `CMD`, `COPY`, `ENTRYPOINT`, and `RUN`. - If relative, is relative to previous `WORKDIR`. - Inherits from base image, so specify your own to be sure. - If not declared at all (like in from-scratch image), defaults to "/".