Docker Certified Associate common mistakes
DCA · Understand the traps and how to avoid them.
Deploying a Compose file with `docker stack deploy` while it still contains `build:`, or relying on `depends_on` to control service start order, then assuming the deploy failed because of a Swarm problem.
Candidates learned Compose with docker-compose up, where build: and depends_on are honoured. docker stack deploy only accepts pre-built images from a registry and ignores ordering hints, so the image is never built and tasks crash-loop with 'image not found' or start out of order.
What to do instead
Build and push the image to a registry the swarm nodes can reach, then reference it in the stack file as image: registry:5000/app:tag. Drop build: and depends_on from swarm stacks entirely; make each service tolerant of its dependencies not being ready yet (retry loops or healthchecks plus restart policies).
Updating a service by re-pushing an image under the same tag and running `docker service update --force mysvc`, then expecting every task to run the new code.
Swarm resolves the image reference when a task is created; --force restarts tasks but does not guarantee a fresh pull of a mutated tag, and nodes may keep the cached digest. Candidates also forget --with-registry-auth for private registries.
What to do instead
Use immutable tags and change the reference explicitly: docker service update --image registry:5000/app:2.0 --with-registry-auth mysvc. Avoid latest in services. Reserve --force for restarting tasks with an unchanged spec.
Putting `COPY . .` (or `ADD . .`) before the dependency-install step in a Dockerfile, and running `apt-get update` in one layer and `apt-get install` in another without cleaning caches.
Every source-file edit invalidates the cache for everything after the COPY, so dependencies reinstall on each build and builds get slow; split update/install layers can pin stale package lists, and uncleaned caches bloat the image.
What to do instead
Copy only the manifest first (COPY package.json . / COPY requirements.txt .), run the install, then COPY . .. Chain package operations in one RUN and clean up: RUN apt-get update && apt-get install -y --no-install-recommends pkg && rm -rf /var/lib/apt/lists/*. Add a .dockerignore.
Tagging an image as `myapp:1.0` and running `docker push myapp:1.0`, then troubleshooting an authorisation/denied error as a credential problem instead of a naming problem.
An image reference without a registry hostname is implicitly resolved against Docker Hub, so the push targets the wrong registry (and the wrong repository path). This also happens when pushing to a private registry on a non-443 port and omitting the port.
What to do instead
Tag with the fully qualified reference before pushing: docker tag myapp:1.0 registry.example.com:5000/team/myapp:1.0, then docker push registry.example.com:5000/team/myapp:1.0. Only then troubleshoot auth with docker login registry.example.com:5000.
Editing `/etc/docker/daemon.json` (for example adding `insecure-registries` or changing `data-root`) and immediately retesting, without reloading or restarting the daemon, and without validating that the JSON parses.
daemon.json is read at daemon start-up, not on every CLI call, so the change appears to have no effect. A trailing comma or merged duplicate key makes the daemon fail to start, which candidates then misread as a broken install.
What to do instead
Validate the JSON first (e.g. python3 -m json.tool /etc/docker/daemon.json or jq .), then systemctl reload docker for reloadable options, otherwise systemctl restart docker. Check journalctl -u docker and docker info to confirm the setting actually took effect. Keep daemon options in daemon.json rather than mixing them with flags in the systemd unit.
Getting 'permission denied while trying to connect to the Docker daemon socket' and 'fixing' it by prefixing every command with `sudo`, or blindly running `chmod 777 /var/run/docker.sock`.
The user is not in the docker group that owns the socket, and the group change from usermod -aG docker only applies to new login sessions. chmod 777 on the socket grants effective root to every local user.
What to do instead
Add the user with sudo usermod -aG docker $USER, then log out and back in (or newgrp docker) so the new group membership is in the session; verify with id. Never loosen socket permissions - membership in docker is root-equivalent, so treat it as such.
Starting two containers with `docker run --name db` and `docker run --name app` without a user-defined network, then expecting the app to reach the database at hostname `db`.
The default bridge network has no embedded DNS, so container names are not resolvable; only user-defined networks provide automatic name/alias resolution. Candidates then fall back to --link or hard-coded container IPs, which break on restart.
What to do instead
Create a user-defined bridge network (docker network create appnet) and attach both containers to it (--network appnet or docker network connect); then db resolves by name. Use Compose/Swarm, which create a network per project/stack by default.
Reversing the publish mapping (`-p 80:8080` when the app listens on 8080 and should be reachable on host port 80), or expecting `--publish` to have any effect with `--network host`.
The syntax is -p HOST_PORT:CONTAINER_PORT and the container port must match what the process actually binds inside the container. In host network mode there is no NAT and no port mapping, so -p is silently ignored.
What to do instead
Read the mapping as host-first: -p 8080:80 publishes container port 80 on host port 8080. Confirm the listen port from the image documentation or docker port. With --network host (Linux only), don't publish ports at all - the container shares the host's network stack.
Trying to pass a Swarm secret to a standalone container (`docker run --secret mysecret ...`), or looking for the secret value in `docker secret inspect` / `docker inspect` output.
Secrets are a Swarm-scoped primitive: they are only injected into service tasks, mounted as a file at /run/secrets/<name> (not as environment variables by default), and the value is deliberately not retrievable after creation. Candidates who tested everything with docker run hit an unsupported flag and misdiagnose it as a version problem.
What to do instead
Consume secrets from Swarm services (docker service create --secret mysecret ...) and read them inside the container from /run/secrets/mysecret. For standalone containers use a read-only bind mount or a Compose secrets file, and never bake secrets into image layers or ENV instructions.
Intending a host bind mount but writing `-v data:/var/lib/app` (which silently creates a named volume), or relying on `-v /nonexistent` to fail, and then wondering why the host directory is empty or the files written by the container are invisible on the host.
The short -v form has no type field: a bare word is treated as a named volume and an absolute path as a bind mount, and Docker creates the source automatically in both cases. Typos in paths therefore fail silently instead of erroring.
What to do instead
Use --mount type=bind,source=/absolute/path,target=/var/lib/app for host paths - it errors loudly if the source is missing - and --mount type=volume,source=data,target=/var/lib/app for named volumes. Always use absolute paths, and remember that anonymous volumes are not removed by --rm unless you also pass -v.