project
└───app
│ │ ...
│ │ Dockerfile
│ │
└───prod.env
└───docker-compose.yml
My docker-compose looks like this:
services:
app:
build:
context: .app
args:
ARG1: val1
ARG2: val2
env_file:
- prod.env
But I’ve tried this too:
services:
app:
build:
context: .app
args:
ARG1: ${ARG1}
ARG2: ${ARG2}
env_file:
- prod.env
My prod.env file looks like this:
ARG1 = 'val1'
ARG2 = 'val2'
But I’ve tried this too:
ARG1=val1
ARG2=val2
I would like for either the values of args or the values from the prod.env file to be passed to the dockerfile.
This is what I’ve tried to get this:
ARG ARG1
ARG ARG2
RUN echo ${ARG1}
RUN echo ${ARG2}
ENV ARG1 ${ARG1}
ENV ARG2 ${ARG2}
RUN echo ${ARG1}
RUN echo ${ARG2}
ENV ARG1 "new val2"
ENV ARG2 "new val2"
RUN echo ${ARG1}
RUN echo ${ARG2}
It always end with blank values.
Any help would be greatly appreciated. I feel like no answers from other posts have worked when I tried them.
To build I use docker-compose --env-file prod.env build
Thanks
Update
Sergio Santiago asked if I could run docker-compose config
and show the results.
Here are the final files I used for this test.
docker-compose:
services:
app:
build:
context: .app
args:
ARG1: val1
ARG2: val2
env_file:
- prod.env
prod.env:
ARG3 = 'val3'
ARG4 = 'val4'
And here is the output of docker-compose --env-file prod.env config
networks:
demo-net: {}
services:
app:
build:
args:
ARG1: val1
ARG2: val2
context: C:projectapp
environment:
ENV: prod.env
ARG3: val3
ARG4: val4
I would like to add that clearly from here getting the variable from the .env file to the docker-compose file is not the issue. I also have a flask app running on the container and through os.environ it is able to use the variables in the .env file. I just can’t figure out how to give the same access to the Dockerfile.
Update 2
More specific information in relation to ErikMD’s answer
prod.env
DOMAIN = 'actualdomain.com'
ENV = 'prod.env'
ENV_NUM = 1
ARG1 = 'value1'
dev.env
DOMAIN = 'localhost'
ENV = 'dev.env'
ENV_NUM = 0
ARG1 = 'value1'
Notice that the value for ARG1 is the same but the other values are different.
docker-compose.yml
version: "3.7"
services:
home:
image: home-${ENV_NUM}
build:
context: .home
args:
ARG1: "${ARG1}"
networks:
- demo-net
env_file:
- ${ENV}
labels:
- traefik.enable=true
- traefik.http.routers.home.rule=Host(`${DOMAIN}`)
- traefik.http.routers.home.entrypoints=web
volumes:
- g::c:sharedrive
...
...
reverse-proxy:
restart: always
image: traefik:v2.6.1-windowsservercore-1809
command:
- --api.insecure=true
- --providers.docker=true
- --entrypoints.web.address=:80
- --providers.docker.endpoint=npipe:////./pipe/docker_engine
ports:
- 80:80
- 443:443
- 8080:8080
networks:
- demo-net
volumes:
- source: \.pipedocker_engine
target: \.pipedocker_engine
type: npipe
networks:
demo-net:
The dots represent other apps that would be formatted the same as home.
dockerfile
FROM python:3.10.3
ARG ARG1="default"
ENV ARG1="${ARG1}"
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
RUN echo "This is argument 1 -> ${ARG1}"
output of docker-compose --env-file prod.env config
networks:
demo-net: {}
services:
home:
build:
args:
ARG1: value1
context: C:MIS-Web-Apphome
environment:
DOMAIN: actualdomain.com
ENV: prod.env
ENV_NUM: '1'
ARG1: value1
image: home-1
labels:
traefik.enable: "true"
traefik.http.routers.home.entrypoints: web
traefik.http.routers.home.rule: Host(`mis.canaras.net`)
networks:
demo-net: null
volumes:
- g::c:sharedrive:rw
...
...
Then I run either docker-compose --env-file prod.env build
or docker-compose --env-file dev.env build
output of build
Step 9/23 : RUN echo "This is argument 1 -> ${ARG1}"
---> Running in 5142850de365
This
is
argument
1
->
Removing intermediate container 5142850de365
Now I call pass the env_file in the command as well as in the actual file because there are variables in there that my docker-compose file needs and variables that my flask app needs. And there is definitely overlap.
Getting the values from the prod.env or dev.env files to docker-compose is not the issue. Neither is getting it to my flask app. The issue is getting those values to the dockerfile.
4
Answers
My solution was annoying which is why it took me so long to figure it out. My dockerfile was using powershell on a windows server, so I had to do this for every argument:
This seems pretty niche especially since using windows containers on a windows server is not my first choice, so check out @ErikMD 's answer if your having issues with env files and whatnot.
You can use the .env file from docker compose so you use the same time that you defined in your service definition:
In this way both can share the same env file, but you still have the drawback of redefining the variables as a placeholder.
This is a suggestion, choose the one that fits better to you
I’m posting a new answer to highlight the various assumptions related to the OP’s question, in particular, the fact that there’s a subtle difference between the
".env"
unique filename and*.env
files (arguments forenv_file:
).But apart from this subtlety, the process to pass arguments from
docker-compose.yml
todocker build -f Dockerfile .
and/ordocker run -e …
is easy, as shown by the comprehensive example below.Minimal working example
Let’s consider the following files in a given directory, say
./docker
.File
docker-compose.yml
:Remark: even if we use a
build:
field, it appears to be a good idea to also add animage:
field to automatically tag the built image; but note that these image names must be pairwise different.File
.env
:File
var.env
:File
Dockerfile
:Experiment session 1
First, as suggested by @SergioSantiago in the comments, a very handy command to preview the effective
docker-compose.yml
file after interpolation isdocker-compose config
:Here, as indicated by the warning, we see there’s an issue for interpolating
ENV_FILE_NUM
despite the fact this variable is mentioned byvar.env
. The reason is thatenv_file
s lines just add new environment variables for the underlyingdocker run -e …
command, but don’t interpolate anything in thedocker-compose.yml
.Contrarily, one can notice that the value
ARG1=.env/ARG1
taken from".env"
is interpolated within theargs:
field ofdocker-compose.yml
, cf. the output line:This very distinct semantics of
".env"
vs.env_file
s is described in this page of the official documentation.Experiment session 2
Next, let us run:
Here, we can see again that the
".env"
values and those offile_env: [ filename.env ]
play different roles that don’t overlap.Furthermore:
ENV ARG3="${ARG3}"
, the value of build-argARG3
is not propagated at runtime (see theARG3=
line in the output above).environment:
orenv_file:
sections in thedocker-compose.yml
file (see theARG3=var.env/ARG3
line in the output above).For more details, see the documentation of the
ARG
directive.Remarks on the
docker-compose --env-file
option use-caseAs mentioned by the OP,
docker-compose
also enjoys a useful CLI option--env-file
(which is precisely named the same way as the very differentenv-file:
field, which is unfortunate, but nevermind).This option allows for the following use-case (excerpt from OP’s code):
File
docker-compose.yml
:File
prod.env
:File
dev.env
:Then run:
docker-compose --env-file prod.env build
,docker-compose --env-file dev.env build
As an aside, even if most of this answer up to now, amounted to illustrating that the
".env"
filename andenv_file:
files enjoy a very different semantics… it is true that they can also be combined "nicely" this way, as suggested by the OP, to achieve this use case.Note in passing that the
docker-compose config
is also applicable to "debug" the Compose specification:docker-compose --env-file prod.env config
,docker-compose --env-file dev.env config
.Now regarding the last question:
it can first be noted that there are two different cases:
prod.env
anddev.env
) can share the same image, so that the difference only lies in the runtime environment variables (not the docker build args).--env-file
, the images should be different (and then adocker-compose --env-file … build
is indeed necessary).It appears that most of the time, case 1. can be achieved (and it is also the case in the question’s configuration, because the
ARG1
values are the same inprod.env
anddev.env
) and can be viewed as more-interesting for the sake of reproducibility (because we are sure that the "prod" image will be the same as the "dev" image).Yet, sometimes it’s impossible to do so and we are "in case 2.", e.g. if the
Dockerfile
has a specific step, maybe related to tests or so, that has to be enabled (resp. disabled) in production mode.So now, let us assume we’re in case 2. How can we pass "everything" from the
--env-file
to theDockerfile
? There is only one solution, namely, extending theargs:
map of thedocker-compose.yml
and include each variable you are interested in, for example:Even if there is no other solution to pass arguments at build time (from
docker-compose
to the underlyingdocker build -f Dockerfile …
), this has the advantage of being "declarative" (only the variables mentioned inargs:
will be actually passed to theDockerfile
).Drawback?
The only drawback I see is that you may have unneeded extra environment variables at runtime (from
docker-compose
to the underlyingdocker run -e …
), such asENV=prod.env
.If this is an issue, you might want to split your
".env"
files like this:File
prod.env
:File
prod-run.env
:(assuming you only want to export these two environment variables at runtime).
Or alternatively, to better follow the usual Do-not-Repeat-Yourself rule, remove
prod-run.env
, then pass these values asdocker-compose
build arguments as mentioned previously:and write in the
Dockerfile
:I already gave an example of these
Dockerfile
directives in section "Experiment session 2".(Sorry for the significant length of this answer BTW 🙂
Another, albeit niche, solution that lets you dynamically pass all the variables from a .env file to the Dockerfile build environment through compose is via a multistage Dockerfile. Copy the file, use it and copy only the result to the second stage.
Passing the path of
dev.env
toweb/Dockerfile
as the only arg in thedocker-compose.yml
:First stage of
web/Dockerfile
:The relevant portion of
prebuild.py
:Following the python script’s injection of needed variables, the second stage of
web/Dockerfile
copies the updated static files to the final image:The last line’s
--from=0
tells docker to pull/PB/static/
from the first stage.The full multistage
web/Dockerfile
:My specific use-case already involved a multipart Dockerfile running a python script to inject certain environmental variables, such as the development/production API domains, into the static files of a site so it worked well for me, but you’re mileage may vary. It’s also worth noting that the environmental variables and the .env file itself won’t be accessible in the second stage of the Dockerfile.