I agree with @meyay so I will respond only focusing on Docker Compose and not CI/CD.
We use Docker compose mainly for local development or on servers where don’t have the resources to use something else or run that on a single host (Swarm, Kubernetes). So we usually have all the required variables. Docker Compose handles variables the same way regardless what subcommand you use. Even docker compose ps
would fail without setting the required variables In rare cases this behavior can be inconvenient, but I don’t even remember when I had any problem with it. I know I had, but I don’t know why and when.
Using your example run the following command:
docker compose config
It will cpmplain about the missing variable. If you run this way:
TEST=1 docker compose config
It will show something like this:
services:
test:
environment:
TEST: "1"
image: scratch
networks:
default: null
networks:
default:
name: vars_default
As you can see it will generate the configuration file replacing the variable with its value. I guess it is because this way Docker does not have to be able to parse different kind of yaml files that it supports. Instead of that, it converts the yaml to a normalized form. Using the config subcommand you can see the config without normalizing it:
TEST=1 docker compose config --no-normalize
services:
test:
environment:
TEST: "1"
image: scratch
but it still replaces the variable (interpolation). For debugging purposes you can disable that too
TEST=1 docker compose config --no-normalize --no-interpolate
services:
test:
environment:
TEST: ${TEST:?}
image: scratch
but it doesn’t make sense with other commands. The main purpose of these environment variables is that you can use the compose file as a template and then render that template. Not just one part but the entire file. If you want those variables to be required by the running container only, you need to build that checking into the entrypoint or the command. Let’s see an example to the command:
services:
test:
image: bash
environment:
- TEST
command:
- -c
- echo $${TEST:?}
# Using double dollars is required so compose will not recognize it as variable.
docker compose config
services:
test:
command:
- -c
- echo $${TEST:?}
environment:
TEST: ""
image: bash
networks:
default: null
networks:
default:
name: vars_default
Notice that TEST: ""
part.
Now run this
TEST=1 docker compose config
services:
test:
command:
- -c
- echo $${TEST:?}
environment:
TEST: "1"
image: bash
networks:
default: null
networks:
default:
name: vars_default
Let’s run the container
docker compose up
[+] Running 1/0
⠿ Container vars-test-1 Created 0.0s
Attaching to vars-test-1
vars-test-1 | bash: line 1: TEST: parameter null or not set
vars-test-1 exited with code 127
TEST=1 docker compose up
[+] Running 1/0
⠿ Container vars-test-1 Recreated 0.0s
Attaching to vars-test-1
vars-test-1 | 1
vars-test-1 exited with code 0
Example to the entrypoint:
entrypoint.sh
#!/usr/bin/env bash
set -eu -o pipefail
: ${TEST:?}
if command -v "$1" &>/dev/null; then
exec "$@"
else
exec bash "$@"
fi
Dockerfile
FROM bash
COPY entrypoint.sh /custom-entrypoint.sh
RUN chmod +x /custom-entrypoint.sh
ENTRYPOINT ["/custom-entrypoint.sh"]
compose.yml
services:
test:
build: .
environment:
- TEST
command:
- -c
- "echo $${TEST}"
The result is the same. Of course the required entrypoint will be different for every application.