LPI DevOps Tools Engineer study materials
LPI DevOps Tools Engineer study resources

LPI DevOps Tools Engineer common mistakes

LPI DevOps Tools Engineer · Understand the traps and how to avoid them.

Mistake 1

Treating a Dockerfile as a shell script and chaining setup steps with 'apt-get install' before 'apt-get update' in separate RUN layers, or using 'apt-get upgrade' inside an image build.

Candidates carry over habits from interactive server administration, where updating and installing in one session is normal. In image builds each RUN is a separate layer, so an update in one layer is cached and stale by the time a later install layer runs, and upgrades make builds non-reproducible.

What to do instead

Combine update and install in a single RUN with cleanup: 'RUN apt-get update && apt-get install -y --no-install-recommends pkg && rm -rf /var/lib/apt/lists/*'. Never run 'apt-get upgrade' in a Dockerfile; rebuild from a refreshed base image instead. Understand that each RUN creates a layer and that cache invalidation depends on the preceding instruction's inputs.

Mistake 2

Assuming data written to a container's writable layer survives a container restart, or expecting 'docker restart' to preserve state after the container was removed and recreated.

The writable layer feels like a normal filesystem, so candidates store databases or uploads there and only discover the loss during a test or after a 'docker rm'. They also confuse restarting a container with recreating it from an image.

What to do instead

Declare a VOLUME or bind mount for any state that must outlive the container, and test by removing and recreating the container. Know that 'docker restart' keeps the writable layer but 'docker rm' plus 'docker run' does not, and that anonymous volumes persist until pruned. Use named volumes for data you need to find and back up later.

Mistake 3

Building multi-stage images without copying artifacts by absolute path from the correct stage, and leaving build toolchains in the final image because the runtime stage is based on the wrong image.

Multi-stage builds are often memorised as syntax rather than understood as separate filesystems. Candidates use relative COPY paths that resolve against the wrong working directory or forget '--from=', so the artifact is missing or the whole builder image leaks into production.

What to do instead

Name stages explicitly ('FROM golang:1.22 AS build') and copy with 'COPY --from=build /app/bin/app /usr/local/bin/app'. Start the final stage from a minimal runtime base, set WORKDIR before relative copies, and verify with 'docker history' and image size that compilers and source are absent from the final image.

Mistake 4

Using an imagePullPolicy of Always with a mutable tag such as 'latest' and then debugging why pods run different code than expected, or pinning a tag but assuming the node will re-pull a changed image.

Candidates learn that 'latest' is discouraged but not the interaction between tags, pull policy, and node-level caching. With a fixed tag and default policy, kubelet reuses the cached image, so a re-pushed tag silently keeps old code running.

What to do instead

Use immutable references (digest or unique version tags) and set imagePullPolicy explicitly: IfNotPresent for pinned versions, Always only when you truly need re-resolution. Remember the default depends on the tag ('latest' defaults to Always). When debugging stale code, check the image ID on the node, not just the tag in the manifest.

Mistake 5

Writing a Deployment without readiness and liveness probes, or configuring a liveness probe that checks a downstream dependency, causing restart loops during partial outages.

Probes look like optional extras and candidates often add only a liveness probe because it is the first one they learn. They then point it at an endpoint that fails when the database is slow, so Kubernetes kills healthy pods and amplifies the outage.

What to do instead

Add a readiness probe for traffic gating and a liveness probe only for genuinely unrecoverable states. Keep liveness checks local to the process (a lightweight health endpoint), put dependency checks in readiness, and set initialDelaySeconds, periodSeconds, and failureThreshold deliberately. Use a startupProbe for slow-starting applications instead of a long initial delay.

Mistake 6

Forgetting that a Service selector must match pod labels exactly, and then concluding the Service is broken when endpoints are empty; also assuming a ClusterIP Service is reachable from outside the cluster.

Labels and selectors are two separate pieces of YAML that must agree, and candidates edit one without the other. The ClusterIP default is also mistaken for a general-purpose entry point, leading to confusion when a browser cannot reach the address.

What to do instead

Verify with 'kubectl get endpoints ' and 'kubectl get pods --show-labels'; an empty endpoint list means the selector matched nothing. Use 'kubectl describe svc' to see the selector in effect. For external access use NodePort, LoadBalancer, or an Ingress, and remember ClusterIP addresses are only routable inside the cluster network.

Mistake 7

Storing credentials and tokens directly in a Dockerfile, a committed values file, or a plain Secret manifest in Git, and treating a Kubernetes Secret as encrypted by default.

It is the fastest way to make an example work, and the base64 encoding of Secret data looks like obfuscation. Candidates do not realise base64 is trivially reversible and that etcd storage is only encrypted if encryption at rest is configured.

What to do instead

Inject secrets at runtime via environment variables from a Secret or a mounted volume, keep them out of image layers and version control, and use .dockerignore plus secret scanning in CI. Enable encryption at rest for Secrets and restrict RBAC 'get'/'list' on secrets. Prefer an external secret manager or sealed-secret workflow for GitOps.

Mistake 8

Granting cluster-admin or wildcard RBAC permissions to a service account or CI runner to 'make it work', and binding roles at cluster scope when namespace scope would do.

RBAC errors are opaque, and widening permissions is the quickest way to unblock a pipeline. Candidates also confuse Role with ClusterRole and RoleBinding with ClusterRoleBinding, so they escalate scope without noticing.

What to do instead

Start from the minimum verbs and resources the workload actually calls, bind with a RoleBinding in the target namespace, and use ClusterRole only for cluster-scoped resources or when sharing rules across namespaces. Test with 'kubectl auth can-i --as=system:serviceaccount:: ' and audit with 'kubectl auth can-i --list'.

Mistake 9

Writing CI pipeline stages that rebuild and retest everything on every commit with no caching or dependency pinning, and using floating base images or unversioned tool images in the pipeline.

Copying a minimal example pipeline produces correctness but poor feedback time, and unpinned images feel convenient. Candidates then face flaky builds and cannot explain why a pipeline that passed yesterday fails today.

What to do instead

Cache dependency directories keyed on lockfile hashes, pin CI tool images by digest or version, and separate fast unit stages from slower integration stages using artifacts between them. Commit lockfiles and use them for installs. Make the pipeline fail on lint and test failures before building images, and keep build, test, and publish as distinct reproducible steps.

Mistake 10

Debugging observability gaps by adding logs only, ignoring metrics and traces, and reading container logs without distinguishing stdout/stderr from files inside the container.

Print-and-pray debugging is familiar, and candidates assume 'docker logs' or 'kubectl logs' shows everything. They miss that only stdout and stderr are captured, that logs vanish when a pod is deleted, and that latency problems need metrics and traces rather than more log lines.

What to do instead

Log structured events to stdout/stderr and let the platform collect them; use 'kubectl logs -p' and '--previous' for crashed containers. Expose a metrics endpoint for rates and latency, add tracing for request flow across services, and use 'kubectl exec' to inspect in-container files. Design alerts on symptoms (error rate, latency, saturation) rather than on raw log strings.