How to make a build arg mandatory during Docker build?

Docker

Docker Problem Overview


Is there any way to make a build argument mandatory during docker build? The expected behaviour would be for the build to fail if the argument is missing.

For example, for the following Dockerfile:

FROM ubuntu

ARG MY_VARIABLE
ENV MY_VARIABLE $MY_VARIABLE

RUN ...

I would like the build to fail at ARG MY_VARIABLE when built with docker build -t my-tag . and pass when built with docker build -t my-tag --build-arg MY_VARIABLE=my_value ..

Is there any way to achieve that behaviour? Setting a default value doesn't really do the trick in my case.

(I'm running Docker 1.11.1 on darwin/amd64.)

EDIT: One way of doing that I can think of is to run a command that fails when MY_VARIABLE is empty, e.g.:

FROM ubuntu

ARG MY_VARIABLE
RUN test -n "$MY_VARIABLE"
ENV MY_VARIABLE $MY_VARIABLE

RUN ...

but it doesn't seem to be a very idiomatic solution to the problem at hand.

Docker Solutions


Solution 1 - Docker

I tested with RUN test -n <ARGvariablename> what @konradstrack mentioned in the original (edit) post... that seems do the job of mandating the variable to be passed as the build time argument for the docker build command:

FROM ubuntu

ARG MY_VARIABLE
RUN test -n "$MY_VARIABLE"
ENV MY_VARIABLE $MY_VARIABLE

Solution 2 - Docker

You can also use shell parameter expansion to achieve this.

Let's say your mandatory build argument is called MANDATORY_BUILD_ARGUMENT, and you want it to be set and non-empty, your Dockerfile could look like this:

    FROM debian:stretch-slim
    MAINTAINER Evel Knievel <[email protected]>
    
    ARG MANDATORY_BUILD_ARGUMENT
    
    RUN \
    # Check for mandatory build arguments
        : "${MANDATORY_BUILD_ARGUMENT:?Build argument needs to be set and non-empty.}" \
    
    # Install libraries
    &&  apt-get update \
    &&  apt-get install -y \
            cowsay \
            fortune \

    # Cleanup
    &&  apt-get clean \
    &&  rm -rf \
            /var/lib/apt/lists/* \
            /var/tmp/* \
            /tmp/* \

    CMD ["/bin/bash", "-c", "/usr/games/fortune | /usr/games/cowsay"]

Of course, you would also want to use the build-argument for something, unlike I did, but still, I recommend building this Dockerfile and taking it for a test-run :)

EDIT

As mentioned in @Jeffrey Wen's answer, to make sure that this errors out on a centos:7 image (and possibly others, I admittedly haven't tested this on other images than stretch-slim):

> Ensure that you're executing the RUN command with the bash shell. > > RUN ["/bin/bash", "-c", ": ${MYUID:?Build argument needs to be set and not null.}"]

Solution 3 - Docker

Another simple way:

RUN test -n "$MY_VARIABLE" || (echo "MY_VARIABLE  not set" && false)

Solution 4 - Docker

You could do something like this...

 FROM ubuntu:14.04
 ONBUILD ARG MY_VARIABLE
 ONBUILD RUN if [ -z "$MY_VARIABLE" ]; then echo "NOT SET - ERROR"; exit 1; else : ; fi

Then docker build -t my_variable_base .

Then build your images based on this...

FROM my_variable_base
...

It's not super clean, but at least it abstracts the 'bleh' stuff away to the base image.

Solution 5 - Docker

Long time ago I had a need to introduce a required (mandatory) ARG, and for better UX include the check at the beginning:

FROM ubuntu:bionic
ARG MY_ARG
RUN [ -z "$MY_ARG" ] && echo "MY_ARG is required" && exit 1 || true

...

RUN ./use-my-arg.sh

But this busts the build cache for every single layer after the initial MY_ARG, because MY_ARG=VALUE is prepended to every RUN command afterwards.

Whenever I changed MY_ARG it would end up rebuilding the whole image, instead of rerunning the last RUN command only.

To bring caching back, I have changed my build to a multi-staged one:

  • The first stage uses MY_ARG and checks it's presence.
  • The second stage proceeds as usual and declares ARG MY_ARG right at the end.
FROM alpine:3.11.5
ARG MY_ARG
RUN [ -z "$MY_ARG" ] && echo "MY_ARG is required" && exit 1 || true

FROM ubuntu:bionic
...
ARG MY_ARG
RUN ./use-my-arg.sh

Since ARG MY_ARG in the second stage is declared right before it's used, all the previous steps in that stage are unaffected, thus cache properly.

Solution 6 - Docker

I cannot comment yet because I do not have 50 reputation, but I would like to add onto @Jan Nash's solution because I had a little difficulty getting it to work with my image.

If you copy/paste @Jan Nash's solution, it will work and spit out the error message that the build argument is not specified.

What I want to add

When I tried getting it to work on a CentOS 7 image (centos:7), Docker ran the RUN command without erroring out.

Solution

Ensure that you're executing the RUN command with the bash shell.

RUN ["/bin/bash", "-c", ": ${MYUID:?Build argument needs to be set and not null.}"]

I hope that helps for future incoming people. Otherwise, I believe @Jan Nash's solution is just brilliant.

Solution 7 - Docker

In case anybody is looking for a the solution but with docker compose build, I used mandatory variables.

version: "3.9"
services:
  my-service:
    build:
      context: .
      args:
        - ENVVAR=${ENVVAR:?See build instructions}

After running docker compose build:

  • Before exporting ENVVAR: Invalid template: "required variable ENVVAR is missing a value: See build instructions"
  • After exporting ENVVAR: build proceeds

Support for Required Environment variables Compose Environment Variables

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkonradstrackView Question on Stackoverflow
Solution 1 - DockerKarthiSView Answer on Stackoverflow
Solution 2 - DockerJan NashView Answer on Stackoverflow
Solution 3 - DockermemsetzeroView Answer on Stackoverflow
Solution 4 - Dockerjohnharris85View Answer on Stackoverflow
Solution 5 - DockerMaciej GolView Answer on Stackoverflow
Solution 6 - DockerJeffrey WenView Answer on Stackoverflow
Solution 7 - DockerchrisView Answer on Stackoverflow