DCA study materials
DCA study resources

Docker Certified Associate common mistakes

DCA · Understand the traps and how to avoid them.

Mistake 1

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).

Mistake 2

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.

Mistake 3

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.

Mistake 4

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.

Mistake 5

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.

Mistake 6

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.

Mistake 7

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.

Mistake 8

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.

Mistake 9

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.

Mistake 10

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.