DevOps & CI
Docker
A to-the-point review of the Docker an SDET gets asked about: images vs containers, the Dockerfile & layer caching, the container lifecycle, volumes & networking, Docker Compose, and — the part that wins the interview — using containers for reproducible, ephemeral test environments and CI. Short explanations, short examples.
01What Docker Is — Containers vs VMs
A container packages an app with its dependencies so it runs the same everywhere. It shares the host OS kernel and isolates only the process, filesystem, and network — so it’s far lighter than a VM.
| Container | Virtual Machine | |
|---|---|---|
| Isolates | process (shares host kernel) | full OS (own kernel) |
| Size / start | MBs, seconds | GBs, minutes |
| Overhead | near-native | hypervisor |
“Works on my machine” disappears: the image is the environment, so dev, CI, and prod run byte-identical dependencies.
02Images vs Containers & Layers
- Image — a read-only template (the blueprint). Built from a
Dockerfile, tagged likemyapp:1.2. - Container — a running (or stopped) instance of an image, with a thin writable layer on top.
- Registry — where images live (Docker Hub, ECR, GHCR);
push/pullmoves them.
An image is a stack of layers, one per build instruction. Layers are cached and shared between images — so ordering your Dockerfile well is the main build-speed lever.
03The Dockerfile & Build Caching
A Dockerfile is the recipe for an image. Each instruction adds a layer.
FROM node:20-alpine # base image
WORKDIR /app
COPY package*.json ./ # copy deps manifest FIRST
RUN npm ci # cached unless package*.json changes
COPY . . # then the source
EXPOSE 3000
CMD ["node", "server.js"] # default process- Cache order matters: copy the dependency manifest and install before copying source, so a code change doesn’t bust the (slow) dependency layer.
CMDvsENTRYPOINT—CMDsets the default command (easily overridden);ENTRYPOINTsets the fixed executable, withCMDas its default args.- Multi-stage builds — build in a heavy image, copy only the artifact into a tiny runtime image; smaller, safer final images.
- Use a
.dockerignoreto keepnode_modules/.gitout of the build context.
COPY . . before RUN npm ci means every source edit reinstalls all dependencies — the #1 slow-build mistake.04Running Containers
The everyday commands an SDET should be fluent in:
| Command | Does |
|---|---|
| docker run | create + start a container from an image |
| docker ps / ps -a | list running / all containers |
| docker exec -it | run a command (e.g. a shell) inside a running container |
| docker logs | view a container’s stdout/stderr |
| docker stop / rm | stop / delete a container |
| docker build -t | build an image from a Dockerfile |
docker run -d --name web -p 8080:3000 -e NODE_ENV=test myapp:1.2
# | | | |
# detached name host:container env var
docker exec -it web sh # shell into it
docker logs -f web # follow the logs
docker rm -f web # stop + remove-p 8080:3000 maps host 8080 → container 3000. Without publishing a port the service isn’t reachable from the host.05Volumes & Data Persistence
A container’s writable layer is ephemeral — delete the container and its data is gone. Volumes persist data outside the container lifecycle.
- Named volume — Docker-managed storage (
-v mydata:/var/lib/postgresql/data); the right choice for databases. - Bind mount — maps a host directory into the container (
-v $(pwd):/app); great for live-reloading source in dev. - tmpfs — in-memory only; fast and wiped on stop.
06Networking
- bridge (default) — containers on the same user-defined bridge network reach each other by name (built-in DNS).
- host — share the host’s network stack (no port mapping, less isolation).
- none — no networking.
Inside a Compose network a service connects to another by its service name as the hostname — e.g. the app reaches the DB at postgres:5432, not localhost.
localhost is the container itself, not the host or another container — use the service/container name (or, from inside a container to the host, host.docker.internal).07Docker Compose
Compose defines a multi-container app in one YAML file and brings it all up with a single command — perfect for a test stack (app + DB + mock server).
services:
app:
build: .
ports: ["8080:3000"]
environment:
DB_URL: postgres://db:5432/test
depends_on: [db]
db:
image: postgres:16
environment:
POSTGRES_DB: test
tmpfs: ["/var/lib/postgresql/data"] # ephemeral test DBdocker compose up -d/down— start / tear down the whole stack.depends_oncontrols start order (not readiness — add a healthcheck for that).- One network is created automatically, so services resolve each other by name.
08Docker for Testing & CI
This is where containers earn their keep for an SDET — disposable, identical environments on every run.
- Ephemeral dependencies — spin up a real Postgres/Redis/Kafka in a container for integration tests instead of mocking, then throw it away.
- Testcontainers — a library (Java/.NET/Python/Node) that starts containers from your test code and tears them down after, with dynamic ports — no leftover state.
- Selenium/Playwright in Docker — official browser images and Selenium Grid containers give consistent, headless browsers in CI.
- CI — GitHub Actions / GitLab run jobs inside containers (
services:/container:), so the build environment is version-pinned and reproducible.
// Testcontainers (Java) — a throwaway Postgres for one test class
@Container
static PostgreSQLContainer<?> db =
new PostgreSQLContainer<>("postgres:16");
// db.getJdbcUrl() gives a dynamic URL; container is removed after tests09Rapid-Fire Q&A
Reveal each answer to self-check, then test yourself with the quiz.
Container vs VM?
A container shares the host kernel and isolates just the process — MBs and seconds; a VM virtualizes a full OS with its own kernel — GBs and minutes.
Image vs container?
The image is a read-only template (the class); a container is a running instance of it (the object) with a writable layer on top.
Why order Dockerfile instructions carefully?
Each instruction is a cached layer; copy the dependency manifest and install before copying source so code changes don’t bust the dependency layer.
CMD vs ENTRYPOINT?
CMD sets the default command (easily overridden at run); ENTRYPOINT sets the fixed executable, with CMD supplying its default arguments.
How do you persist data?
Use a volume (named volume for DBs, bind mount for live source) — the container’s own writable layer is deleted with the container.
How do two containers talk?
On a shared user-defined/Compose network they resolve each other by service or container name via Docker’s built-in DNS — not localhost.
What does -p 8080:3000 do?
Publishes (maps) host port 8080 to container port 3000 so the service is reachable from the host.
What is a multi-stage build?
Build in a heavy image, then COPY only the artifact into a small runtime image — smaller, more secure final images.
What is Docker Compose for?
Defining and running a multi-container app (app + DB + mocks) from one YAML file with a single up/down.
How does Docker help testing?
Reproducible, disposable environments — real dependencies via Testcontainers and consistent browsers in CI, with clean state every run.
Image too big — what do you do?
Use a slim/alpine base, multi-stage builds, a .dockerignore, and combine RUN steps to cut layers.
depends_on guarantees readiness?
No — it only controls start order; add a healthcheck (or retry in the app) to wait until the dependency is actually ready.