Recently, I was working on opentelemetry and needed to customize its Docker image to meet our application requirements. Specifically, I needed to support a writable directory to mount rotating certificates for mTLS connections. The official OpenTelemetry Docker image uses FROM scratch, which results in a non-writable directory. It had been a while since I last worked with Dockerfiles, and I recently faced a stupid issue that costed me a couple of hours. The root cause was that I was using CMD instead of ENTRYPOINT.
I found it valuable to share this via my blog to clarify the distinction between CMD and ENTRYPOINT in a Dockerfile, and to explain when and how each should be used to prevent any confusion.
Difference between Entrypoint and CMD
In Dockerfile, CMD and ENTRYPOINT are both instructions that define what command should be run inside the container when it starts. Here’s a clear distinction between the two:
CMD
- Specifies the default command to be executed when the container starts.
- If
CMDis specified multiple times in a Dockerfile, only the last one takes effect. - It can be overridden using
argsin the K8s Kustomization spec or by passing arguments todocker runafter the image name.
Example:
CMD ["otelcol-contrib", "--config=config.yaml"]
ENTRYPOINT
- Defines the executable that will run when the container starts.
- Unlike
CMD, ENTRYPOINT parameters are not overridden when K8s Kustomization spec ordocker runspecifies a command args. - If you need to pass arguments to the
ENTRYPOINT, you can do so usingCMD.
Example:
ENTRYPOINT ["otelcol-contrib"]
CMD ["--config=config.yaml"]
When to use ENTRYPOINT vs CMD?
When deciding between ENTRYPOINT and CMD in your Dockerfile, it’s crucial to follow these guidelines:
ENTRYPOINT should specify the path to the main executable binary or process that runs inside the container. It defines the primary command that K8/Docker executes when the container starts.
CMD should provide default arguments for the
ENTRYPOINTcommand. These arguments are optional and can be overridden by users as needed when running the container.
My Dockerfile Tale: From Error to Clarity
Click here to uncover the mistake, the debugging journey, and how I fixed the issue!
What I Did Wrong Initially
I started by writing the Dockerfile and used CMD to define the command that runs when the container starts. My intention was to specify the binary and pass the default path for the config file, like this:
From alpine:3.18
...
...
CMD ["otelcol-contrib"]
CMD ["--config=config.yaml"]
Next, I created a simple Kustomization to deploy this image, thinking it would only override the CMD config path. However, when I deployed the pod, it failed!
# Kustomization deployment file
apiVersion: apps/v1
kind: Deployment
metadata:
name: otelcollector
spec:
replicas: 1
template:
metadata:
labels:
app.kubernetes.io/name: otelcollector
spec:
containers:
- name: otelcol
image: docker.io/otel-metrics-collector
args:
- --config=/etc/otel/config.yaml
volumeMounts:
- name: collector-config
mountPath: /etc/otel
volumes:
- name: collector-config
configMap:
items:
- key: config.yaml
path: config.yaml
The pod failed to start with the following error:
Error response from daemon: failed to create task for container:
failed to create shim task: OCI runtime create failed:
runc create failed: unable to start container process:
exec: "--config=/etc/otel/config.yaml": stat --config=/etc/otel/config.yaml:
no such file or directory: unknown.
My Debugging Journey:
- Initially, I suspected the issue was with mounting the config file via configMap due to the
no such file or directory error, so I spent time troubleshooting there without success. - I then modified the Dockerfile to replace the existing
CMDwithCMD ["sh", "-c", "sleep infinity"]to keep the container running for debugging. - To my surprise, the config file was in the correct location, and the binary worked as expected with the config file.
- Confused, I revisited the
args: --config=/etc/otel/config.yamlin the Kustomization file. While Googling, I discovered that args might be overridden, which reminded me that I had mistakenly usedCMDinstead ofENTRYPOINT. - Further research clarified that
ENTRYPOINTshould define the executable, which cannot be overridden, whileCMDshould only provide the default arguments that can be overridden.
How the Issue Was Fixed!
Using ENTRYPOINT as shown below resolved the issue and taught me a valuable lesson worth sharing with my colleagues and curious minds on the internet.
From alpine:3.18
...
...
ENTRYPOINT ["otelcol-contrib"]
CMD ["--config=config.yaml"]
Thanks for reading! I hope my mistake provided some useful insights for you.
