|
| 1 | +# This is a multi-stage Dockerfile, with a selectable first stage. With this |
| 2 | +# approach we get: |
| 3 | +# |
| 4 | +# 1. Separation of dependencies needed to build our app in the 'build' stage |
| 5 | +# and those needed to run our app in the 'final' stage, as we don't want |
| 6 | +# the build-time dependencies to be included in the final Docker image. |
| 7 | +# |
| 8 | +# 2. Support for either building our app for the architecture of the base |
| 9 | +# image using MODE=build (the default) or for externally built app |
| 10 | +# binaries (e.g. cross-compiled) using MODE=copy. |
| 11 | +# |
| 12 | +# In total there are four stages consisting of: |
| 13 | +# - Two possible first stages: 'build' or 'copy'. |
| 14 | +# - A special 'source' stage which selects either 'build' or 'copy' as the |
| 15 | +# source of binaries to be used by ... |
| 16 | +# - The 'final' stage. |
| 17 | + |
| 18 | + |
| 19 | +### |
| 20 | +### ARG DEFINITIONS ########################################################### |
| 21 | +### |
| 22 | + |
| 23 | +# This section defines arguments that can be overriden on the command line |
| 24 | +# when invoking `docker build` using the argument form: |
| 25 | +# |
| 26 | +# `--build-arg <ARGNAME>=<ARGVALUE>`. |
| 27 | + |
| 28 | +# MODE |
| 29 | +# ==== |
| 30 | +# Supported values: build (default), copy |
| 31 | +# |
| 32 | +# By default this Dockerfile will build our app from sources. If the sources |
| 33 | +# have already been (cross) compiled by some external process and you wish to |
| 34 | +# use the resulting binaries from that process, then: |
| 35 | +# |
| 36 | +# 1. Create a directory on the host called 'dockerbin/$TARGETPLATFORM' |
| 37 | +# containing the already compiled app binaries (where $TARGETPLATFORM |
| 38 | +# is a special variable set by Docker BuiltKit). |
| 39 | +# 2. Supply arguments `--build-arg MODE=copy` to `docker build`. |
| 40 | +ARG MODE=build |
| 41 | + |
| 42 | + |
| 43 | +# BASE_IMG |
| 44 | +# ======== |
| 45 | +# |
| 46 | +# Only used when MODE=build. |
| 47 | +ARG BASE_IMG=alpine:3.18 |
| 48 | + |
| 49 | + |
| 50 | +# CARGO_ARGS |
| 51 | +# ========== |
| 52 | +# |
| 53 | +# Only used when MODE=build. |
| 54 | +# |
| 55 | +# This ARG can be used to control the features enabled when compiling the app |
| 56 | +# or other compilation settings as necessary. |
| 57 | +ARG CARGO_ARGS |
| 58 | + |
| 59 | + |
| 60 | +### |
| 61 | +### BUILD STAGES ############################################################## |
| 62 | +### |
| 63 | + |
| 64 | + |
| 65 | +# ----------------------------------------------------------------------------- |
| 66 | +# Docker stage: build |
| 67 | +# ----------------------------------------------------------------------------- |
| 68 | +# |
| 69 | +# Builds our app binaries from sources. |
| 70 | +FROM ${BASE_IMG} AS build |
| 71 | +ARG CARGO_ARGS |
| 72 | + |
| 73 | +RUN apk add --no-cache rust cargo openssl-dev |
| 74 | + |
| 75 | +WORKDIR /tmp/build |
| 76 | +COPY . . |
| 77 | + |
| 78 | +# `CARGO_HTTP_MULTIPLEXING` forces Cargo to use HTTP/1.1 without pipelining |
| 79 | +# instead of HTTP/2 with multiplexing. This seems to help with various |
| 80 | +# "spurious network error" warnings when Cargo attempts to fetch from crates.io |
| 81 | +# when building this image on Docker Hub and GitHub Actions build machines. |
| 82 | +# |
| 83 | +# `cargo install` is used instead of `cargo build` because it places just the |
| 84 | +# binaries we need into a predictable output directory. We can't control this |
| 85 | +# with arguments to cargo build as `--out-dir` is unstable and contentious and |
| 86 | +# `--target-dir` still requires us to know which profile and target the |
| 87 | +# binaries were built for. By using `cargo install` we can also avoid needing |
| 88 | +# to hard-code the set of binary names to copy so that if we add or remove |
| 89 | +# built binaries in future this will "just work". Note that `--root /tmp/out` |
| 90 | +# actually causes the binaries to be placed in `/tmp/out/bin/`. `cargo install` |
| 91 | +# will create the output directory for us. |
| 92 | +RUN CARGO_HTTP_MULTIPLEXING=false cargo install \ |
| 93 | + --locked \ |
| 94 | + --path . \ |
| 95 | + --root /tmp/out/ \ |
| 96 | + ${CARGO_ARGS} |
| 97 | + |
| 98 | + |
| 99 | +# ----------------------------------------------------------------------------- |
| 100 | +# Docker stage: copy |
| 101 | +# ----------------------------------------------------------------------------- |
| 102 | +# Only used when MODE=copy. |
| 103 | +# |
| 104 | +# Copy binaries from the host directory 'dockerbin/$TARGETPLATFORM' directory |
| 105 | +# into this build stage to the same predictable location that binaries would be |
| 106 | +# in if MODE were 'build'. |
| 107 | +# |
| 108 | +# Requires that `docker build` be invoked with variable `DOCKER_BUILDKIT=1` set |
| 109 | +# in the environment. This is necessary so that Docker will skip the unused |
| 110 | +# 'build' stage and so that the magic $TARGETPLATFORM ARG will be set for us. |
| 111 | +FROM ${BASE_IMG} AS copy |
| 112 | +ARG TARGETPLATFORM |
| 113 | +ONBUILD COPY dockerbin/$TARGETPLATFORM /tmp/out/bin/ |
| 114 | + |
| 115 | + |
| 116 | +# ----------------------------------------------------------------------------- |
| 117 | +# Docker stage: source |
| 118 | +# ----------------------------------------------------------------------------- |
| 119 | +# This is a "magic" build stage that "labels" a chosen prior build stage as the |
| 120 | +# one that the build stage after this one should copy application binaries |
| 121 | +# from. It also causes the ONBUILD COPY command from the 'copy' stage to be run |
| 122 | +# if needed. Finally, we ensure binaries have the executable flag set because |
| 123 | +# when copied in from outside they may not have the flag set, especially if |
| 124 | +# they were uploaded as a GH actions artifact then downloaded again which |
| 125 | +# causes file permissions to be lost. |
| 126 | +# See: https://github.com/actions/upload-artifact#permission-loss |
| 127 | +FROM ${MODE} AS source |
| 128 | +RUN chmod a+x /tmp/out/bin/* |
| 129 | + |
| 130 | + |
| 131 | +# ----------------------------------------------------------------------------- |
| 132 | +# Docker stage: final |
| 133 | +# ----------------------------------------------------------------------------- |
| 134 | +# Create an image containing just the binaries, configs & scripts needed to run |
| 135 | +# our app, and not the things needed to build it. |
| 136 | +# |
| 137 | +# The previous build stage from which binaries are copied is controlled by the |
| 138 | +# MODE ARG (see above). |
| 139 | +FROM ${BASE_IMG} AS final |
| 140 | + |
| 141 | +# Copy binaries from the 'source' build stage into the image we are building |
| 142 | +COPY --from=source /tmp/out/bin/* /usr/local/bin/ |
| 143 | + |
| 144 | +# Build variables for uid and guid of user to run container |
| 145 | +ARG RUN_USER=rrdpit |
| 146 | +ARG RUN_USER_UID=1012 |
| 147 | +ARG RUN_USER_GID=1012 |
| 148 | + |
| 149 | +# Install required runtime dependencies |
| 150 | +RUN apk add --no-cache bash libgcc openssl tini tzdata util-linux |
| 151 | + |
| 152 | +# Create the user and group to run the application as |
| 153 | +RUN addgroup -g ${RUN_USER_GID} ${RUN_USER} && \ |
| 154 | + adduser -D -u ${RUN_USER_UID} -G ${RUN_USER} ${RUN_USER} |
| 155 | + |
| 156 | +# Create the data directories and create a volume for them |
| 157 | +VOLUME /data |
| 158 | +RUN mkdir -p /data/source /data/target && \ |
| 159 | + chown -R ${RUN_USER_UID}:${RUN_USER_GID} /data |
| 160 | + |
| 161 | +# Install a Docker entrypoint script that will be executed when the container |
| 162 | +# runs |
| 163 | +COPY docker/entrypoint.sh /opt/ |
| 164 | +RUN chown ${RUN_USER}: /opt/entrypoint.sh |
| 165 | + |
| 166 | +# Switch to our applications user |
| 167 | +USER ${RUN_USER} |
| 168 | + |
| 169 | +# Use Tini to ensure that our application responds to CTRL-C when run in the |
| 170 | +# foreground without the Docker argument "--init" (which is actually another |
| 171 | +# way of activating Tini, but cannot be enabled from inside the Docker image). |
| 172 | +ENTRYPOINT ["/sbin/tini", "--", "/opt/entrypoint.sh"] |
0 commit comments