Dockerfile CMD Command not using variable
Abstract: Resolve Docker CMD variable expansion failures by switching to shell form or using ENV with a script wrapper.
2026-08-18
Created: 2026-08-18 by
UserComp.com Editors
The CMD instruction in a Dockerfile does not expand shell variables because it executes as an exec-form JSON array, bypassing the shell entirely. To resolve this, you must either use the shell form of CMD which invokes /bin/sh or /bin/bash for expansion, or explicitly pass the variable to a script that handles the substitution at runtime. This ensures your container behaves predictably when environment-specific values are required during startup.
Understanding the CMD Instruction Forms
Docker provides two distinct syntaxes for the CMD instruction: the exec form and the shell form. The exec form is written as a JSON array, such as CMD ["executable", "param1", "param2"]. This form is preferred for most applications because it avoids spawning an intermediate shell process, leading to faster container starts and cleaner signal handling. However, this efficiency comes with a trade-off: no shell processing occurs. Variables like $HOME or custom environment variables defined via ENV are not expanded by the shell before execution.
In contrast, the shell form looks like CMD command param1 param2. When Docker encounters this format, it automatically wraps the command in /bin/sh -c (on Linux) or cmd.exe /S /C (on Windows). This wrapper allows standard shell features like variable expansion, pipes, and redirects to function correctly. If your CMD relies on string interpolation, using the shell form is the direct solution, though it adds a slight overhead due to the extra shell process.
Why Variable Expansion Fails in Exec Form
When you write CMD ["echo", "$VAR"], Docker passes the literal string $VAR to the executable. The executable receives exactly what was typed, without any prior modification. This is often confusing for developers accustomed to bash scripts where variables are automatically resolved. The Docker daemon parses the JSON array and hands control directly to the specified binary. Since no shell is involved, the concept of a variable environment context within the command string is ignored. This behavior is intentional to ensure portability and consistency across different host systems.
Solution 1: Using the Shell Form
The simplest way to fix variable expansion issues is to switch to the shell form of CMD. Instead of defining your command as a JSON array, define it as a plain string. For example, change CMD ["python", "app.py"] to CMD python app.py. This tells Docker to run the command through /bin/sh -c, which will expand any environment variables present in the string before executing the application.
This approach is particularly useful for simple scripts or commands that rely heavily on shell features. However, be aware that if your application crashes, the exit code might be reported from the shell rather than the application itself, which can complicate monitoring and health checks. Additionally, signals sent to the container are handled by the shell, which may not forward them correctly to the child process unless specific care is taken.
Solution 2: Passing Variables via ENV and Script Wrapper
For production-grade containers, it is often better to keep the exec form while ensuring variables are available to the application. You can achieve this by defining the variable using the ENV directive in your Dockerfile. For instance, ENV MY_VAR="value" sets the variable for all subsequent layers and at runtime. Then, in your entrypoint script or application code, read this environment variable.
If you need complex logic involving multiple variables, create a small shell script that acts as an entry point. Use ENTRYPOINT ["/entrypoint.sh"] in exec form, but let the script handle the variable expansion internally. Inside entrypoint.sh, you can use standard bash syntax to build arguments dynamically. This method combines the benefits of the exec form (proper signal handling) with the flexibility of shell scripting. It is a robust pattern recommended for complex deployments.
Solution 3: Overriding Variables at Runtime
Variables defined in the Dockerfile via ENV can be overridden when running the container using the -e flag. For example, docker run -e MY_VAR="new_value" my_image. This allows you to maintain a single image while injecting different configurations for development, testing, and production environments. When using the shell form of CMD, these runtime variables are expanded just like those set in the Dockerfile.
It is important to note that ENV instructions persist in the image history. If you have sensitive data, consider using build-time arguments (ARG) instead, which do not appear in the final image metadata. However, ARG values are not available at runtime unless explicitly passed again or converted to ENV. Understanding the distinction between build-time and runtime contexts is crucial for secure container management.
Best Practices for Variable Management
To maintain clarity and reliability, adopt a consistent strategy for variable usage. Prefer the exec form for CMD and ENTRYPOINT to ensure proper process management. Use ENV for default configuration values that can be overridden at runtime. Avoid hardcoding secrets or dynamic paths directly in the Dockerfile. Instead, rely on environment variables or mounted configuration files.
Additionally, validate your variables at startup. If a required variable is missing, your entrypoint script should fail gracefully with a clear error message. This prevents silent failures where the container starts but the application behaves unexpectedly due to null or empty variables. Logging the initial environment state can also aid in debugging issues related to variable propagation.
Frequently Asked Questions
Can I use ARG inside CMD?
No, ARG variables are only available during the build phase. They disappear after the image is built unless you explicitly copy their value into an ENV variable. Therefore, CMD cannot reference ARG directly at runtime.
How do I debug variable expansion issues?
Run the container with docker run -it my_image /bin/sh to enter an interactive shell. Then, manually echo the variables you expect to see. This helps determine if the issue lies in how the variable was set or how the command is executed.
Is there a performance penalty for using the shell form?
Yes, the shell form spawns an additional process (/bin/sh). While negligible for most applications, this overhead can matter in high-throughput microservices or resource-constrained environments where every millisecond counts.
How do I pass secrets securely?
Avoid using ENV for secrets as they are visible in docker inspect. Use Docker Secrets, environment variables passed via -e at runtime, or mount secret files from the host system to keep sensitive data out of the image layers.
What happens if a variable is undefined?
In the shell form, an undefined variable expands to an empty string, which may cause command errors depending on the application. In the exec form, the literal string $VAR is passed, likely causing the application to fail if it expects a valid value.