What is the difference between COPY vs ADD, and CMD vs ENTRYPOINT?
Quick Answer
COPY moves files from host to image. ADD does the same but also auto-extracts tar archives and can fetch URLs. CMD sets default arguments you can override at runtime. ENTRYPOINT sets the fixed executable that always runs, with CMD providing its default arguments.
Detailed Answer
Think of COPY as a simple courier who picks up a package from your desk and places it exactly where you say. ADD is a courier who also opens boxes for you: hand it a tar.gz archive and it unpacks it automatically, and it can even fetch packages from a URL. For the other pair, ENTRYPOINT is the permanent manager of a restaurant who greets every customer, while CMD is the daily special that can be swapped out. The manager (ENTRYPOINT) always runs, but what they serve (CMD) can be changed by the customer (whoever runs docker run).
COPY is the straightforward Dockerfile instruction for moving files and directories from the build context into the image. It takes a source path relative to the build context and a destination path inside the image. ADD does everything COPY does but adds two extras: it auto-extracts recognized compressed archives (tar, gzip, bzip2, xz) into the destination, and it can download files from remote URLs. However, Docker best practices strongly recommend using COPY for all standard file transfers because its behavior is explicit and predictable. Use ADD only when you specifically need tar extraction. For remote downloads, using curl or wget inside a RUN instruction is better because you can verify checksums, retry on failure, and clean up the downloaded file in the same layer.
Under the hood, both COPY and ADD create new image layers. Docker computes a checksum of the source files to decide if the layer cache can be reused. For ADD with URLs, Docker cannot reliably cache because the remote content might change without the URL changing, which leads to unpredictable builds. For tar extraction, ADD detects the archive format from file headers and extracts in place during layer creation. CMD and ENTRYPOINT are metadata instructions that change the image config rather than the filesystem. They do not create new layers but are stored as JSON in the image manifest. When both are present in exec form (the bracket syntax), Docker joins them together: the ENTRYPOINT array becomes the command and the CMD array gets appended as arguments.
In production Dockerfiles, the ENTRYPOINT plus CMD pattern is the standard for services. You set ENTRYPOINT to the application binary so the container always runs that binary, and CMD provides sensible default flags that operators can override. For example, ENTRYPOINT ["/order-service"] with CMD ["--port=8080", "--env=production"] means running docker run order-service --port=9090 replaces only the CMD part while the entrypoint stays fixed. This pattern matters in Kubernetes because the command field overrides ENTRYPOINT while the args field overrides CMD. Health checks, init containers, and sidecar proxies all depend on this predictable behavior.
A common trap is mixing shell form and exec form. CMD npm start (shell form) runs through /bin/sh -c, which means the node process becomes a child of the shell and does not receive SIGTERM signals properly, causing slow container shutdowns. CMD ["npm", "start"] (exec form) runs npm directly as PID 1, allowing proper signal handling. Another gotcha is defining CMD in a parent base image and then setting ENTRYPOINT in your Dockerfile without re-specifying CMD. The inherited CMD from the base image becomes the argument to your new ENTRYPOINT, which is almost never what you want. Always set both explicitly when using the ENTRYPOINT plus CMD pattern. Finally, remember that docker run arguments replace CMD entirely rather than appending to it, which surprises engineers who expect additive behavior.