How can I use variables in Dockerfile ENTRYPOINT in JSON format?
Hello! I have several applications that are built using the same Dockerfile structure, but they only differ in the compilation directory name and the executable file name. This forces me to use multiple Dockerfiles.
I want to create a single parameterized template Dockerfile. I don’t have issues with directory names, but I’m running into difficulties with the ENTRYPOINT layer.
If ENTRYPOINT is described in JSON (exec) format, the variable doesn’t get substituted:
ENTRYPOINT ["dotnet", "${APPLICATION}.dll"]
If I use shell form, parameterization works:
ENTRYPOINT ["sh","-c","exec dotnet ${APPLICATION}.dll"]
But what should I do if it’s crucial that ENTRYPOINT remains in JSON format?
I found a temporary solution through creating a symbolic link, but it requires an additional layer and doesn’t look very elegant:
RUN ln -s "${APPLICATION}.dll" /app/app.dll
ENTRYPOINT ["dotnet","/app/app.dll"]
Here’s an example of my Dockerfile (APP is the service name):
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build-env
WORKDIR /app
ENV DOTNET_NUGET_SIGNATURE_VERIFICATION=false
COPY . .
RUN cd __APP__ && dotnet publish -c Release -o publish /p:DebugType=None /p:DebugSymbols=false
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build-env /app/__APP__/publish .
ENTRYPOINT ["dotnet", "__APP__.dll"]
How can I properly use variables in ENTRYPOINT Dockerfile in JSON format without creating additional layers and symbolic links?
Docker ENTRYPOINT JSON Format Doesn’t Support Variable Substitution at Build Time
The Docker ENTRYPOINT in JSON format doesn’t support variable substitution at build time because this form executes the command directly through exec() rather than through a shell where variable processing occurs. However, there are several elegant solutions for parameterizing ENTRYPOINT without creating additional layers.
Table of Contents
- The Main Problem with JSON ENTRYPOINT Format
- Solution Using Templating
- Solution Through a Startup Script
- Solution Using ARG Variables
- Solution Using Build Tools
- Comparison of Approaches
- Recommended Approach
The Main Problem with JSON ENTRYPOINT Format
As explained in the Docker documentation, when ENTRYPOINT is specified in JSON format (exec form), the command is executed directly through exec() rather than through a shell. This means environment variables are not substituted at build time:
# ❌ Doesn't work - variable is not substituted
ENTRYPOINT ["dotnet", "${APPLICATION}.dll"]
Unlike the shell form, where variables are processed:
# ✅ Works, but not JSON format
ENTRYPOINT ["sh","-c","exec dotnet ${APPLICATION}.dll"]
According to Joe Yates’ blog, this is an important limitation of the JSON format that often causes confusion.
Solution Using Templating
The cleanest approach is to use templating at build time. Create an ENTRYPOINT template and substitute variables during the build process:
Step 1: Create an entrypoint.template file
#!/bin/sh
exec dotnet ${APPLICATION}.dll
Step 2: In Dockerfile, use the template
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build-env
WORKDIR /app
ENV DOTNET_NUGET_SIGNATURE_VERIFICATION=false
COPY . .
RUN cd __APP__ && dotnet publish -c Release -o publish /p:DebugType=None /p:DebugSymbols=false
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build-env /app/__APP__/publish .
# Create a template with variables
RUN echo "#!/bin/sh\nexec dotnet \${APPLICATION}.dll" > /entrypoint.template
# Substitute variables and create an executable script
RUN sed -i "s/__APP__/${APPLICATION}/g" /entrypoint.template && \
chmod +x /entrypoint.template
# Use the script in ENTRYPOINT JSON format
ENTRYPOINT ["/entrypoint.template"]
This approach allows you to maintain the JSON format of ENTRYPOINT and avoid symbolic links.
Solution Through a Startup Script
Create a universal startup script that can work with different applications:
Startup script start.sh:
#!/bin/sh
# Define the application name from the variable or default
APP_NAME=${APPLICATION:-app}
# Start the application
exec dotnet "${APP_NAME}.dll"
Dockerfile:
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build-env
WORKDIR /app
ENV DOTNET_NUGET_SIGNATURE_VERIFICATION=false
COPY . .
RUN cd __APP__ && dotnet publish -c Release -o publish /p:DebugType=None /p:DebugSymbols=false
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build-env /app/__APP__/publish .
COPY start.sh /app/
# Set the application variable
ARG APPLICATION=app
ENV APPLICATION=${APPLICATION}
# Make the script executable and use it
RUN chmod +x /app/start.sh
ENTRYPOINT ["/app/start.sh"]
This approach is very flexible and allows you to easily add new startup logic.
Solution Using ARG Variables
Although ARG variables are not directly substituted into ENTRYPOINT JSON, you can use them to create a dynamic Dockerfile:
ARG APPLICATION=app
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build-env
WORKDIR /app
ENV DOTNET_NUGET_SIGNATURE_VERIFICATION=false
COPY . .
RUN cd ${APPLICATION} && dotnet publish -c Release -o publish /p:DebugType=None /p:DebugSymbols=false
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build-env /app/${APPLICATION}/publish .
# Create a file with the correct application name
RUN echo "${APPLICATION}.dll" > /app/appname.txt
# Use a script to read the filename
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/sh
APP_NAME=$(cat /app/appname.txt)
exec dotnet "${APP_NAME}"
Solution Using Build Tools
Use build tools to generate the Dockerfile:
Using a Makefile:
build:
@sed "s/__APP__/$(APPLICATION)/g" Dockerfile.template > Dockerfile
docker build -t myapp:$(APPLICATION) .
run:
docker run -e APPLICATION=$(APPLICATION) myapp:$(APPLICATION)
Dockerfile.template:
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build-env
WORKDIR /app
ENV DOTNET_NUGET_SIGNATURE_VERIFICATION=false
COPY . .
RUN cd __APP__ && dotnet publish -c Release -o publish /p:DebugType=None /p:DebugSymbols=false
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build-env /app/__APP__/publish .
ENTRYPOINT ["dotnet", "__APP__.dll"]
Comparison of Approaches
| Approach | Advantages | Disadvantages |
|---|---|---|
| Templating | Clean solution, maintains JSON format | Requires additional build steps |
| Startup Script | Flexible, easily extensible | May add slight overhead |
| ARG + file | Uses standard Docker mechanisms | Requires creating an additional file |
| Build Tools | Maximum flexibility | Requires external tools |
Recommended Approach
For most cases, I recommend the startup script solution, because it:
- Maintains the JSON format of ENTRYPOINT
- Doesn’t require additional Docker layers
- Is easy to maintain and extend
- Works with any environment variables
- Allows adding error handling, logging, etc.
Example final Dockerfile:
ARG APPLICATION=app
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build-env
WORKDIR /app
ENV DOTNET_NUGET_SIGNATURE_VERIFICATION=false
COPY . .
RUN cd ${APPLICATION} && dotnet publish -c Release -o publish /p:DebugType=None /p:DebugSymbols=false
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build-env /app/${APPLICATION}/publish .
COPY start.sh /app/
# Set the application variable
ENV APPLICATION=${APPLICATION}
# Make the script executable and use it
RUN chmod +x /app/start.sh
ENTRYPOINT ["/app/start.sh"]
#!/bin/sh
# Process environment variables
APP_NAME=${APPLICATION:-app}
# Add logging
echo "Starting application: $APP_NAME"
# Start the application
exec dotnet "${APP_NAME}.dll"
This approach solves your problem elegantly without creating additional layers or symbolic links.
Sources
- Dockerfile reference | Docker Docs
- Docker ENTRYPOINT, CMD and
runArguments - Joe Yates’ Blog - Integrating Docker Environment Variables in ENTRYPOINT Array | Baeldung on Ops
- How do I use Docker environment variable in ENTRYPOINT array? - Stack Overflow
- JSONArgsRecommended | Docker Docs
- Docker ARG, ENV and .env - a Complete Guide · vsupalov.com