compose: Undefined default build arg is sent as empty string to docker build, instead of not sent

In issue #3281 compose < 1.8.0-rc1 incorrectly sent undefined default build args as 'None' string to docker build, it was fixed by PR #3449 (included in compose 1.8.0-rc1) by sending an empty string instead to docker build.

I think it would be better to just not send the undefined build args to docker build, so that the default value for the build arg defined in Dockerfile would apply.

Scenario:

Files

Dockerfile:

FROM ubuntu:14.04

ARG FOO=1
RUN echo "-${FOO}-"

CMD /bin/bash

docker-compose.yml:

version: '2'

services:
  test:
    build:
      context: .
      args:
        - FOO

Execution:

$ ./docker-compose-1.8.0-rc1 --verbose config
networks: {}
services:
  test:
    build:
      args:
        FOO: ''
      context: /home/riccardi/git/ses-docker/test-default-build-arg
version: '2.0'
volumes: {}

$ ./docker-compose-1.8.0-rc1 --verbose build
compose.config.config.find: Using configuration files: ./docker-compose.yml
docker.auth.auth.load_config: Found 'auths' section
docker.auth.auth.parse_auth: Found entry (registry=u'docker-registry-stag.systran.net:5000', username=u'systran')
compose.cli.command.get_client: docker-compose version 1.8.0-rc1, build 9bf6bc6
docker-py version: 1.8.1
CPython version: 2.7.9
OpenSSL version: OpenSSL 1.0.1e 11 Feb 2013
compose.cli.command.get_client: Docker base_url: http+docker://localunixsocket
compose.cli.command.get_client: Docker version: KernelVersion=4.4.0-21-generic, Os=linux, BuildTime=2016-04-26T23:30:23.291099901+00:00, ApiVersion=1.23, Version=1.11.1, GitCommit=5604cbe, Arch=amd64, GoVersion=go1.5.4
compose.service.build: Building test
compose.cli.verbose_proxy.proxy_callable: docker build <- (pull=False, stream=True, nocache=False, tag=u'testdefaultbuildarg_test', buildargs={u'FOO': ''}, rm=True, forcerm=False, path='/home/riccardi/git/ses-docker/test-default-build-arg', dockerfile=None)
docker.api.build._set_auth_headers: Looking for auth config
docker.api.build._set_auth_headers: Sending auth config (u'docker-registry-stag.systran.net:5000')
compose.cli.verbose_proxy.proxy_callable: docker build -> <generator object _stream_helper at 0x7f8a18739b40>
Step 1 : FROM ubuntu:14.04
 ---> 8f1bd21bd25c
Step 2 : ARG FOO=1
 ---> Using cache
 ---> a8c68a88ef6d
Step 3 : RUN echo "-${FOO}-"
 ---> Running in c03d7e477353
--
 ---> 17f6ac07ea06
Removing intermediate container c03d7e477353
Step 4 : CMD /bin/bash
 ---> Running in 47164716758d
 ---> 5ef78af6532b
Removing intermediate container 47164716758d
Successfully built 5ef78af6532b
compose.cli.verbose_proxy.proxy_callable: docker close <- ()
compose.cli.verbose_proxy.proxy_callable: docker close -> None

Issue

Expected result:

prints -1-.

Actual result:

prints --.

<1.8.0-rc1 result:

prints -None-.

Details

compose 1.8.0-rc1 behavior is better than previous compose versions: it could be accepted as a correct behavior. However, I believe the behavior could be improved further by expecting -1- as result, not --: if docker compose has no value for the build arg, then it should let docker build apply its own default value that comes from the Dockerfile.

Changing from -- to -1- is a breaking change, so I suggest to release it in 1.8.0 if this behavior is accepted.

Without this feature, I have to duplicate the default value both in Dockerfile and in .env read by docker-compose (or just in .env, but in this case the Dockerfile cannot be used alone, without docker-compose), and I think the best place for the default value is in the Dockerfile only.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 10
  • Comments: 33

Commits related to this issue

Most upvoted comments

I’ve encountered this impacting use of proxy related variables, and part of the it is that the behaviour between args and environment is not consistent.

Reading https://docs.docker.com/compose/compose-file/#/args and https://docs.docker.com/compose/compose-file/#/environment, one would expect the same behaviour from both.

Given a service defined as follows:

services:
  jenkins:
    image: jenkins:latest
    build:
      context: ./jenkins
      args:
        - HTTP_PROXY
        - HTTPS_PROXY
        - NO_PROXY
        - http_proxy
        - https_proxy
        - no_proxy
    environment:
      - HTTP_PROXY
      - HTTPS_PROXY
      - NO_PROXY
      - http_proxy
      - https_proxy
      - no_proxy
      - JAVA_OPTS=-Djenkins.install.runSetupWizard=false
      - JENKINS_OPTS=--prefix=/jenkins

If you happen to only have http_proxy & https_proxy defined but not HTTP_PROXY & HTTPS_PROXY defined what you will see during the build is that HTTP_PROXY and HTTPS_PROXY are set to ‘’, which causes anything checking the environment for these variables to assume that their existance means no proxy is defined, rather than moving onto checking the lowercase versions as well.

This can cause all sorts of opaque issues building with docker-compose behind proxies because the behaviour for the environment versions of these is different. I’ve discovered that apk will not check for http_proxy if it finds HTTP_PROXY set to an empty string.

You could argue the software should check for both variations, however given the difference in behavour when dealing with environment settings, such as if you have an image built, and using the same unset/set variables listed above, and run docker-compose run -u root jenkins env you will not see HTTP_PROXY or HTTPS_PROXY appearing in the output.

This is because for environment:

    environment:
        - SOMEVAR

SOMEVAR is considered optional, and is only picked up from the environment of the user if defined.

It seems like it would be much better to have the behaviour of args and environment consistent, and since environment has been around for longer, I would suggest that following it’s behaviour rather than changing environment to follow the newer behaviour of args will have less of an impact.

Perhaps it would be better if instead of having:

    build:
        args:
            - SOMEVAR

pass through a variable as being empty, require that someone provide ‘=’, with nothing after it to indicate that it’s intended to override with an empty value.

This would match the existing behaviour for environment, and avoid surprising behaviour.

Override SOMEVAR in Dockerfile only if defined in user environment:

    build:
        args:
            - SOMEVAR

OR

    build:
        args:
            SOMEVAR:

Override SOMEVAR in Dockerfile with blank value

    build:
        args:
            - SOMEVAR=

or

    build:
        args:
            SOMEVAR: ""

Override SOMEVAR in Dockerfile with different value

    build:
        args:
            - SOMEVAR=my_value

or

    build:
        args:
            SOMEVAR: my_value

This seems to give the flexibility needed to be able to override with blank values, while also retaining the ability to pass through certain env variables only if set.

AFAIK the problem still exists and is why I’ve had to avoid docker compose for building of images due to the inconsistency

Let’s create activity here to keep the issue open, because I assume the issue is still there if no compose developer talked about it here, and we have recent documented workarounds…

Something like this would be amazing:

# docker-compose.yml
services:
  app:
    build:
      context: .
      args:
        - NODE_VERSION=${cat .node-version}
# Dockerfile
FROM node:${NODE_VERSION}

This is a real issue that needs to be resolved

FIve years 😢 With this, and no support for build time secrets, docker-compose is more of a headache than it is useful!

One thing that might help is if a maintainer could confirm if any of the proposed behaviour changes would be acceptable if a PR was put together?

This issue does not have a true workaround. The workarounds presented do not solve the core issue as requested by @thomas-riccardi :

  • having a default for ARGs being defined in Dockerfile only (which would be the apt place as it allows one to use Dockerfiles without compose and keep defaults behavior consistent)
  • not having to specify these defaults in multiple places in order to achieve the above

The workaround given by @idetoile does not work for ARGs. They still behave as when this issue was first open, regardless of having been able to use bash inline defaults.

Furthermore, use of bash inline defaults are documented nowhere, apart from being nonchalantly glossed over in the section on Scope for ARGs in Dockerfile reference, so this particular issue is the documentation for the matter, which also adds to the frustration.

Anyway,

Given compose file:

version: '3'

services: 
    test:
        build:
            context: .
            args:
                FOO: ${FOO:-}

and Dockerfile:

FROM alpine
ARG FOO=${FOO:-2}
RUN echo "-- $FOO --"

Issuing docker build --build-arg FOO --no-cache .

Results in the relevant lines being:

Step 3/3 : RUN echo "-- $FOO --"
 ---> Running in 48cd1eaaaf2c
-- 2 --

Whereas running docker-compose build results in:

Step 3/3 : RUN echo "-- $FOO --"
 ---> Running in 935f9a469215
--  --

Which means that the 2017 merge of PR to @thomas-riccardi issue in docker upstream solved this issue in docker itself, but compose still does something wrong when passing the argument.

If we change the behaviour as you suggest, it’s impossible to override a build arg’s default value with an empty value. i.e. if the Dockerfile contains FOO=1, I can’t override that with FOO=.

FOO= and FOO are not the same thing, if you want to override ARG FOO=1 from the Dockerfile with empty string, you can either put args: [ FOO= ] in the docker-compose.yml, or in the .env, so I don’t see any issue here.

However, your 3rd point is valid: indeed, docker engine treats --build-arg FOO as --build-arg FOO= if FOO is not defined in the docker build process environment.

I understand your point about keeping the same behavior on both engine and compose. I’ll open an issue in docker engine for that.

I’d like to point out that this docker engine feature is not documented anywhere (neither in https://docs.docker.com/engine/reference/commandline/build/, nor in https://docs.docker.com/engine/reference/builder/). I suggest to not document it for compose either so it’ll be easier to change the behavior without it being considered as a breaking change.

Thanks

PS: maybe this issue can be closed for now; I’m not sure what procedure to follow in this case.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

My workaround example:

# docker-compose.yml
services:
  app:
    build:
      args:
        CONTAINERPILOT_VERSION: "${CONTAINERPILOT_VERSION:-}"
# Dockerfile
ARG CONTAINERPILOT_VERSION
ENV CONTAINERPILOT_VERSION=${CONTAINERPILOT_VERSION:-"3.8.0"}

with this implementation I need to define default version only in Dockerfile, and my docker-compose file is proxying ARG from optional env var

@agilgur5 I like your docker-compose build myservice --build-arg MYBUILDARG=argggg idea! You probably should open a new issue for that.