envfile-compat: one .env file for docker run and Compose

A small tool that shows where docker run --env-file and Docker Compose read the same .env file differently and converts the file into the form docker run expects.

The problem

docker run --env-file and the env_file: key of Docker Compose both read .env files, but they follow different rules. docker run takes every value literally: quotes, inline # comments and trailing spaces become part of the value, and lines with an export prefix or with KEY = value are errors. Compose and Bash strip the quotes, drop inline comments and expand ${VAR}. A file that works with source .env and with Compose therefore passes "My App", including the quotes, to a container started with docker run.

The issue is documented:

  • docker/cli#3630, "Handle quotes in --env-file values consistently with Linux/WSL2 sourcing", has 134 reactions and 58 comments and has been open since 2022.
  • Three pull requests that change the parser are open and unmerged (#3660, #6046, #6805). Changing the parser would alter how existing files are read.
  • The workaround described in the issue is a second, unquoted copy of every .env file. dotenv-linter checks style. It does not show how each tool reads a value.

How it works

envfile-compat contains a model of each reader. The docker run model follows the rules of pkg/kvfile in docker/cli: leading whitespace is removed, # lines are skipped, the key ends at the first =, and everything after it is the value. A line without = takes its value from the environment. The Compose model follows the dotenv parser of compose-go. It handles the export prefix, KEY: value, single quotes (literal), double quotes (escapes, variable expansion, line breaks) and unquoted values with # comments. References such as ${VAR} and ${VAR:-default} are resolved as Compose does, first from the calling environment and then from earlier lines of the file.

envfile-compat check FILE reads the file with both models and lists every variable whose values differ, plus every line that docker run rejects. The exit code is 1 when anything differs, so the check can run in CI. With --bash the file is also sourced in a clean bash with set -a; this executes the file. envfile-compat docker FILE prints the variables as Compose reads them, in the literal KEY=value form that docker run expects.

This is the output for a three-line file:

$ cat .env
APP_NAME="My App"
GREETING=hello # say hi
PORT=8080

$ envfile-compat check .env
APP_NAME
  docker run  '"My App"'
  compose     'My App'
GREETING
  docker run  'hello # say hi'
  compose     'hello'

2 variable(s) differ. `envfile-compat docker .env` writes a file that
docker run --env-file reads the way Compose does.

$ envfile-compat docker .env
APP_NAME=My App
GREETING=hello
PORT=8080

The converted file can be passed directly, for example with docker run --env-file <(envfile-compat docker .env) myimage. The tool needs Python 3.10 or later and has no dependencies.

Testing

The unit tests run both models over a corpus file with quoting, comment, escape and interpolation cases, an export line, KEY = value, KEY: value, an indented line, trailing spaces and a key without a value. Further tests cover the interpolation operators (-, :-, +, :+, :?), multi-line values, unterminated quotes, the Bash reader, and the conversion: the output of envfile-compat docker must parse back to the same values.

A second group of tests compares the models with the real tools. docker compose config must return exactly the values of the Compose model for the corpus, and env inside a container started with docker run --env-file must return exactly the values of the docker run model. The converted file must also read back through docker run unchanged. These tests are skipped when Docker is not available. CI runs the suite on Python 3.10 to 3.13 with Docker Compose v2 and the alpine:3.20 image.

Limitations

  • Values with line breaks cannot be expressed in the format docker run reads. envfile-compat docker reports them as an error.
  • check --bash executes the file and should only be used on trusted files.
  • The two readers are reimplementations. Their agreement with Docker is checked for the cases in the test corpus.

The code and installation instructions are at github.com/Jwrede/envfile-compat.