Select Page
Automation Testing

Docker Fundamentals for Testers: Images, Containers, Networks, and Volumes

A practical Docker for testers guide covering images, containers, networks, and volumes, with QA workflows and troubleshooting.

Asiq Ahamed

Founder & CEO, Codoid.

Posted on

13/08/2026

Docker Fundamentals For Testers Images, Containers, Networks, And Volumes

If you’ve ever lost an afternoon to a “works on my machine” bug, a mismatched library version, or a database that wouldn’t reset cleanly between test runs, this guide is for you. Docker for testers isn’t about becoming a DevOps engineer, it’s about gaining direct control over the environments your tests depend on: which database version is running, how services talk to each other, and what state persists between runs. This guide walks through the four Docker building blocks every tester should know, images, containers, networks, and volumes, with practical QA workflows, a hands-on step-by-step exercise, and troubleshooting tips for when things go wrong. If you’d rather have a team design and maintain this kind of containerized test infrastructure for you, our QA automation services can help.

What Docker concepts should testers understand first?

This Docker for testers guide breaks down the concepts that matter most for QA work. Testers learning Docker should understand four core objects: images define the environment, containers run that environment, networks connect containers, and volumes preserve data outside a container’s disposable writable layer. Together, these concepts let QA teams create repeatable test environments, isolate dependencies, reproduce defects, and reset application state predictably.

Key takeaways

  • A Docker image is an immutable, layered package containing the files and dependencies required to run software.
  • A container is a runnable instance of an image with its own writable container layer.
  • A Docker network controls how containers communicate with one another, the host, and external systems.
  • A Docker volume stores persistent data independently of a container’s lifecycle and is managed by Docker.
  • User-defined networks are especially useful in test environments because containers can communicate by name instead of relying on changing container IP addresses.
  • For reproducible tests, pin important dependencies to deliberate image versions, and use image digests when an exact immutable image is required.

What is Docker from a tester’s perspective?

Docker is a platform for building and running applications in containers. Docker environments are composed of objects such as images, containers, networks, and volumes.

For a tester, Docker is less about “virtualizing a server” and more about creating controlled test dependencies on demand.

Instead of asking every tester to install PostgreSQL, Redis, Nginx, a specific runtime, and multiple supporting services directly on their workstation, a team can define those components as containers.

A test environment might look like this:

Test runner
  |
  v
Web application container
  |
  v
Database container
  |
  v
Persistent Docker volume

All containers communicate through
an isolated Docker network.

The environment can then be created, tested, destroyed, and recreated without manually rebuilding each dependency.

This is particularly useful for integration tests, API tests, automated regression suites, CI environments, compatibility testing, defect reproduction, and running Selenium tests inside Docker containers for browser-based suites.

What is a Docker image?

A Docker image is an immutable package containing the files required to create a container. Images typically include application binaries, runtime libraries, configuration defaults, operating-system-level files, and other dependencies. Docker images are composed of filesystem layers, as Docker’s own documentation explains.

For example:

postgres:17
nginx:alpine
redis:8
ubuntu:24.04

Each reference identifies an image and usually a tag.

Running:

docker run nginx:alpine

tells Docker to create and start a container using the nginx:alpine image. If the image is not available locally, docker run can pull it before starting the container.

Why images matter to testers

Images help define the software environment used during testing.

Suppose a defect occurs against PostgreSQL 17 but not against another database version. A tester can launch the required PostgreSQL image instead of manually reinstalling the database.

Images also improve consistency across:

Developer machine
    |
    v
Tester machine
    |
    v
CI pipeline
    |
    v
Shared QA environment

However, tags need careful handling. Docker documentation notes that image tags are mutable: a publisher can update which image a tag references. A digest, by contrast, identifies an exact image version.

For ordinary exploratory testing, a version tag may be sufficient:

docker pull postgres:17

For tightly controlled regression or compatibility testing, a digest can provide stronger reproducibility:

docker pull repository/image@sha256:<digest>

What is a Docker container?

A Docker container is a runnable instance of an image. Containers can be created, started, stopped, restarted, inspected, connected to networks, given persistent storage, and deleted.

The distinction is important:

IMAGE
Reusable definition
    |
    | docker run
    v
CONTAINER
Running instance

One image can create many independent containers.

For example:

docker run -d --name web-1 nginx:alpine
docker run -d --name web-2 nginx:alpine

Both containers use the same image but have separate container identities and writable layers.

What happens when a container writes files?

Docker images themselves remain immutable. When Docker creates a container, it adds a writable container layer above the image’s read-only layers. Changes made during execution are written into that container-specific layer.

Conceptually:

+-------------------------+
| Writable container data |
+-------------------------+
| Application layer       |
+-------------------------+
| Dependency layer        |
+-------------------------+
| Base image layer        |
+-------------------------+

If the container is destroyed, data stored only in its writable layer is not a reliable persistence mechanism. Docker recommends storage mechanisms such as volumes when data needs to survive independently of the container.

This distinction is fundamental for testers because restarting a container and replacing a container are not the same operation.

Why Docker fundamentals matter for software testing

Docker allows testers to control infrastructure variables that otherwise create inconsistent results.

Consider a failing integration test involving an application, PostgreSQL, and a specific configuration.

Without containerization, differences might come from:

  • database versions;
  • installed libraries;
  • conflicting host ports;
  • leftover test data;
  • machine-specific configuration;
  • service startup state.

With Docker, those dependencies can be explicitly defined.

A QA team can therefore treat infrastructure as part of the test preconditions. Teams building this kind of repeatable infrastructure often lean on dedicated QA automation services to design and maintain it at scale.

Known image
+ Known configuration
+ Known network
+ Known storage state
= More reproducible test environment

Docker does not automatically make tests deterministic. The application, external systems, clocks, random data, concurrency, and other factors can still introduce variability. It does, however, give testers explicit control over several important environmental dependencies.

How do Docker images, containers, networks, and volumes work together?

A typical test workflow follows this sequence:

  • Docker obtains or builds an image.
  • Docker creates a container from that image.
  • Docker attaches the container to a network if communication is required.
  • Docker attaches a volume if data must persist independently of the container.
  • The tester executes tests against the running system.
  • Logs, container metadata, network settings, and persisted data can be inspected when a failure occurs.
  • Containers can be removed and recreated to restore a known environment.

Docker’s docker inspect command exposes low-level information about Docker-managed objects, while docker logs retrieves container log output made available through the configured logging mechanism.

How do Docker networks work?

A Docker network provides connectivity and isolation for containers.

Docker Engine provides several network drivers, including bridge, host, none, overlay, ipvlan, and macvlan. The bridge driver is the default network driver.

For most local testing, the most important concept is the user-defined bridge network.

Create one with:

docker network create qa-network

Start a web server on it:

docker run -d \
  --name web \
  --network qa-network \
  nginx:alpine

Then start another container and access web by name:

docker run --rm \
  --network qa-network \
  alpine \
  wget -qO- http://web

Containers attached to a custom network use Docker’s embedded DNS service, allowing container names or aliases to be resolved without hard-coding container IP addresses.

Container ports versus published ports

A container can communicate with another container over a Docker network without necessarily exposing that service to the host.

If a tester needs to access the container from the host machine, a port can be published:

docker run -d \
  --name web \
  -p 8080:80 \
  nginx:alpine

Conceptually:

Tester browser
localhost:8080
    |
    v
Host port 8080
    |
    v
Container port 80

You can inspect published mappings with:

docker port web

Docker provides docker port specifically for viewing a container’s published port mappings.

What is a Docker volume?

A Docker volume is persistent storage managed by Docker and mounted into one or more containers. Docker recommends volumes as the preferred mechanism for data generated and used by containers when that data needs to persist independently of a particular container.

Create a named volume:

docker volume create qa-db-data

Attach it to PostgreSQL:

docker run -d \
  --name qa-db \
  -e POSTGRES_PASSWORD=testpass \
  -v qa-db-data:/var/lib/postgresql/data \
  postgres:17

The important relationship is:

PostgreSQL container
    |
    v
/var/lib/postgresql/data
    |
    v
Docker volume: qa-db-data

If the container is replaced, the named volume can be attached to another container.

That makes volumes useful when testing:

  • database migrations;
  • restart behavior;
  • application upgrades;
  • persistence after container replacement;
  • backup and restore procedures.

Docker volume vs. bind mount: what should testers use?

A volume is managed by Docker. A bind mount, by contrast, maps an explicit file or directory from the host filesystem into the container.

For example:

docker run --rm \
  --mount type=bind,src="$PWD/test-data",dst=/tests/data \
  my-test-image

Bind mounts are particularly useful when a tester needs a container to read files directly from the working directory, such as:

  • test scripts;
  • fixtures;
  • configuration files;
  • reports;
  • generated artifacts.

Docker’s documentation specifically identifies bind mounts as appropriate when files need to be accessible from both the container and the host.

S. No Factor Named volume Bind mount
1 Managed by Docker Host filesystem
2 Host path required No Yes
3 Typical QA use Database/state persistence Test code, fixtures, reports
4 Portability Less dependent on host paths Depends on host directory structure
5 Host editing Indirect Direct
6 Good default for application-generated persistent data Yes Usually not the first choice

Step-by-step: Build a Docker test environment

The following exercise combines containers, networks, volumes, port publishing, and test commands.

1. Create an isolated test network

docker network create qa-network

Why: The network gives test services an isolated communication space and allows containers on that custom network to resolve each other through Docker networking.

Expected result: Docker returns the newly created network ID.

Verify it:

docker network ls

2. Create persistent database storage

docker volume create qa-db-data

Why: PostgreSQL state should survive replacement of the database container if persistence is part of the scenario.

Verify:

docker volume ls

3. Start PostgreSQL

docker run -d \
  --name qa-db \
  --network qa-network \
  -e POSTGRES_PASSWORD=testpass \
  -e POSTGRES_DB=appdb \
  -v qa-db-data:/var/lib/postgresql/data \
  postgres:17

Inspect its state:

docker ps

View startup output:

docker logs qa-db

4. Check database readiness from another container

Run a temporary PostgreSQL client container:

docker run --rm \
  --network qa-network \
  postgres:17 \
  pg_isready -h qa-db -U postgres

The test container accesses the database using the container name qa-db, rather than a manually discovered IP address.

5. Insert controlled test data

docker run --rm \
  --network qa-network \
  -e PGPASSWORD=testpass \
  postgres:17 \
  psql -h qa-db -U postgres -d appdb \
  -c "CREATE TABLE IF NOT EXISTS test_runs (
    id SERIAL PRIMARY KEY,
    status VARCHAR(20)
  );
  INSERT INTO test_runs(status) VALUES ('PASS');"

Expected result: The table is created if necessary and one test record is inserted.

6. Start a web target

docker run -d \
  --name qa-web \
  --network qa-network \
  -p 8080:80 \
  nginx:alpine

Verify from the host:

curl http://localhost:8080

Or test connectivity entirely inside Docker:

docker run --rm \
  --network qa-network \
  alpine \
  wget -qO- http://qa-web

This demonstrates an important networking distinction:

Host      -> localhost:8080 -> qa-web:80
Container -> qa-web:80

The second path uses Docker networking directly and does not require the test container to use the host-published port.

7. Inspect the environment after a failure

Useful tester commands include:

docker ps -a
docker logs qa-web
docker logs qa-db
docker inspect qa-web
docker inspect qa-db
docker network inspect qa-network
docker volume inspect qa-db-data
docker stats

docker inspect returns detailed Docker object information, while docker stats provides a live stream of resource usage for running containers.

8. Replace the database container without deleting its data

Remove the database container:

docker rm -f qa-db

Recreate it using the same volume:

docker run -d \
  --name qa-db \
  --network qa-network \
  -e POSTGRES_PASSWORD=testpass \
  -e POSTGRES_DB=appdb \
  -v qa-db-data:/var/lib/postgresql/data \
  postgres:17

Query the stored record again:

docker run --rm \
  --network qa-network \
  -e PGPASSWORD=testpass \
  postgres:17 \
  psql -h qa-db -U postgres -d appdb \
  -c "SELECT * FROM test_runs;"

If the original named volume remains intact, the database state is available to the replacement container. Volumes are specifically designed to persist data independently of a container’s lifecycle.

9. Clean up the test environment

Remove the test containers:

docker rm -f qa-web qa-db

Remove the network:

docker network rm qa-network

If the test requires a completely fresh database on the next run, remove the volume too:

docker volume rm qa-db-data

This final command is intentionally separate. Deleting a container does not mean that a named volume should automatically be treated as disposable.

Practical QA example: reproducing a database migration defect

Consider a QA team investigating an application upgrade that fails only when existing database data is present.

Preconditions

The team needs:

  • PostgreSQL 17;
  • the previous application release;
  • existing database records;
  • the new application release;
  • a repeatable migration sequence.

Test process

  • Start PostgreSQL using a named volume.
  • Start the previous application version.
  • Populate representative records.
  • Stop and replace the application container.
  • Keep the database volume unchanged.
  • Start the new application version.
  • Execute the migration.
  • Verify schema and data.
  • Capture container logs if migration fails.

The key design decision is that application containers can be disposable while database state is deliberately persistent.

For a clean-install test, the tester removes the volume before execution.

For an upgrade test, the tester preserves it.

The same Docker mechanism therefore supports two materially different test scenarios merely by controlling storage lifecycle.

Docker image vs. container vs. network vs. volume

S. No Docker object What it represents Typical lifecycle Tester use
1 Image Immutable application/environment package Built or pulled, then reused Pin software and dependency versions
2 Container Runnable instance of an image Create, start, stop, replace Run the system under test or dependencies
3 Network Connectivity boundary between containers Create, connect services, remove Reproduce service-to-service communication
4 Volume Docker-managed persistent data Create, mount, preserve or delete Control database and stateful test data

A useful mental model is:

Image     = blueprint
Container = running instance
Network   = communication path
Volume    = persistent state

That analogy is deliberately simplified, but it is sufficient for most introductory testing workflows.

Docker best practices for testers

Pin deliberate dependency versions

Avoid treating latest as a precise test precondition. Docker documentation notes that tags are mutable. Use deliberate version tags and consider digests when the exact image contents must remain fixed.

Record the image reference alongside test results when infrastructure version is relevant to defect reproduction.

Prefer user-defined networks for multi-container tests

Create explicit networks rather than depending on ad hoc connectivity.

Custom Docker networks use Docker’s embedded DNS service, making named service communication practical and reducing dependence on container IP addresses.

Treat containers as replaceable

Do not use a running container as an undocumented, hand-configured QA server.

If a tester manually enters a container and modifies packages or configuration, record the change in a Dockerfile or environment definition when it is needed again.

The goal should be to reproduce the environment from declarations rather than from memory.

Separate persistent and disposable state

Decide explicitly whether every test needs:

  • fresh state;
  • seeded state;
  • preserved state;
  • migrated state.

Use volumes accordingly.

Capture diagnostic evidence before teardown

Before destroying a failing environment, collect relevant evidence:

docker ps -a
docker logs <container>
docker inspect <container>
docker network inspect <network>
docker stats --no-stream

This prevents an automated cleanup stage from removing information required for root-cause analysis.

Use a Dockerfile for repeatable custom test environments

A Dockerfile is the text-based definition Docker uses to build an image. Common instructions include FROM, WORKDIR, COPY, and RUN, as covered in Docker’s Dockerfile reference.

For example:

FROM python:3.13-slim

WORKDIR /tests

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["pytest", "-v"]

This is more reproducible than installing the test framework manually inside a running container before each execution.

Common Docker mistakes testers make

S. No Mistake Why it happens Impact Recommended fix
1 Treating an image and container as the same thing Both are discussed as “Docker environments” Confusing lifecycle and state behavior Remember that the image creates the container
2 Using latest as a fixed version The name sounds deterministic Test dependencies can change Pin an explicit version or digest
3 Storing important data only in the container layer Persistence was not planned State disappears when the container is replaced Use a volume
4 Hard-coding container IP addresses IP appears during troubleshooting Tests become fragile Use names on a custom network
5 Publishing every service port Port mapping seems required for communication Extra host exposure and conflicts Publish only services the host must reach
6 Reusing dirty database volumes accidentally Cleanup only removes containers Tests inherit old data Remove or recreate volumes when clean state is required
7 Deleting volumes automatically Aggressive cleanup scripts Useful failure state may be lost Separate container cleanup from storage cleanup
8 Changing containers manually Fast during investigation Environment becomes unreproducible Capture repeatable changes in Dockerfiles/configuration

Need Help Building a Containerized QA Environment?

Talk to Our QA Experts

Troubleshooting Docker tests

Why can one container not reach another?

The most likely causes are that the containers are not attached to the same network, the destination service is not listening on the expected interface or port, or the test is using the wrong hostname.

Verify the networks:

docker inspect <container>
docker network inspect <network>

If necessary, attach an existing container:

docker network connect qa-network <container>

Docker documents docker network connect as the command for attaching an existing container to a network.

Why does localhost fail between containers?

Inside a container, localhost normally refers to that container itself, not a different application container.

For container-to-container communication on a user-defined Docker network, use the destination container or service name:

http://api:8080

rather than:

http://localhost:8080

This distinction is one of the most common networking errors in containerized integration tests.

Why did my test data disappear?

The data was probably written to the container’s writable layer and the container was subsequently removed or replaced.

Docker states that data in the writable container layer does not persist after the container is destroyed.

Use a named volume when persistence is required:

-v qa-data:/path/in/container

Why is old data appearing in a supposedly clean test?

The container may be new while its named volume is old.

Inspect volumes:

docker volume ls
docker volume inspect qa-db-data

If the scenario requires completely fresh state, explicitly remove the appropriate test volume before recreating the service.

Do this only when the data is known to be disposable.

Why can’t I access a container from my browser?

The application’s port may not have been published to the host.

Check:

docker ps
docker port <container>

If the application listens on port 80 in the container, run it with an appropriate mapping such as:

docker run -p 8080:80 ...

The browser then accesses:

http://localhost:8080

Why doesn’t docker exec ... bash work?

Some minimal images do not include Bash or other troubleshooting utilities.

Depending on the image, /bin/sh may exist:

docker exec -it <container> sh

Docker also provides docker debug for debugging minimal images or containers where standard utilities may be absent.

Useful Docker tools and implementation options for testers

Docker CLI

The CLI is the fastest way to understand Docker fundamentals because it exposes each object directly.

Frequently useful commands include:

docker image ls
docker pull
docker run
docker ps
docker logs
docker inspect
docker exec
docker network ls
docker network inspect
docker volume ls
docker volume inspect
docker stats

Docker groups container operations such as run, exec, inspect, logs, stop, rm, and stats under its container command set.

Docker Compose

Once a test environment contains multiple services, Docker Compose can make the environment easier to define and reproduce.

A Compose file can describe services, volumes, networks, and related configuration in YAML, per the Compose file reference.

For example:

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    networks:
      - qa

  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: testpass
      POSTGRES_DB: appdb
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - qa

networks:
  qa:

volumes:
  db-data:

Run it with:

docker compose up -d

And remove the containers and default resources with:

docker compose down

Compose creates networking for the application’s services, and services on its default network can be reached using service names.

For testers, this makes the environment definition reviewable and suitable for source control instead of leaving setup instructions scattered across shell history or documentation.

Limitations and risks of Docker-based testing

Docker improves environmental control, but testers should understand its boundaries.

Containers are not identical to every production environment

A containerized test environment may still differ from production in orchestration, networking, storage, security policies, kernel behavior, infrastructure services, or external integrations.

Docker should therefore complement, not automatically replace, testing in representative higher environments.

Persistent state can make tests non-deterministic

Volumes are useful precisely because they survive container replacement. That same property can accidentally carry state between tests.

Test suites should define whether state is intentionally preserved or deliberately destroyed.

Bind mounts introduce host dependencies

A bind mount directly references the host filesystem, so behavior can depend on host paths and permissions. Docker’s volume documentation distinguishes this from Docker-managed volumes, which are less tied to host directory structure.

Published ports can create conflicts

Two test environments cannot normally bind the same host port simultaneously without additional configuration.

Parallel test execution should use dynamic ports, isolated CI workers, or another deliberate allocation strategy.

Containers should not be treated as a security boundary by assumption

Test infrastructure often handles credentials, tokens, datasets, and access to internal services. Teams should apply appropriate security controls rather than assuming that putting a process in a container makes unsafe configuration acceptable.

Conclusion

This Docker for testers guide showed how Docker’s core objects map onto everyday QA work. An image defines the environment. A container runs it. A network controls communication. A volume controls persistent state.

Those four concepts are enough to build useful QA workflows:

Choose known images
    v
Create disposable containers
    v
Connect services predictably
    v
Persist only intentional state
    v
Run tests
    v
Capture diagnostics
    v
Reset and reproduce

The next practical step is to take one existing integration-test dependency, such as PostgreSQL, Redis, or a mock API, and run it in Docker. Or, if your suite is browser-based, see how to run Selenium tests inside Docker for a concrete starting point. Then add a user-defined network and deliberately test both clean and persistent-state scenarios. Once that workflow is comfortable, move the multi-container environment into Docker Compose so that the infrastructure definition can live alongside the test code.

Frequently Asked Questions

  • Do testers need to know Docker?

    Testers do not need Docker for every project, but it is highly useful when test environments depend on databases, APIs, browsers, queues, caches, service emulators, or other reproducible infrastructure. Understanding images, containers, networks, and volumes is usually enough to begin running and troubleshooting containerized test environments effectively.

  • What is the difference between a Docker image and a container?

    A Docker image is the immutable package used to create containers. A container is a runnable instance of that image with runtime configuration and a writable container layer. Multiple independent containers can be created from the same image.

  • Does deleting a container delete its Docker volume?

    A named volume has a lifecycle separate from an individual container and is intended to preserve data independently of the container. Test cleanup should therefore manage containers and named volumes deliberately rather than treating them as the same resource.

  • Should testers use Docker volumes or bind mounts?

    Use Docker volumes when application-generated data such as database state should persist independently of containers. Use bind mounts when files need a direct relationship with the host filesystem, for example test scripts, fixtures, local source code, or reports.

  • Why should tests use container names instead of container IP addresses?

    Container IP addresses are infrastructure details that should generally not become hard-coded test configuration. Containers on custom Docker networks can use Docker's embedded DNS service, allowing tests and services to communicate through names instead.

  • Does Docker Compose replace Docker?

    No. Docker Compose defines and manages multi-container applications using Docker's underlying container, network, image, and volume concepts. It makes coordinated environments easier to describe and operate but does not eliminate the need to understand those fundamentals.

  • What Docker commands should a tester learn first?

    Start with docker pull, docker run, docker ps, docker logs, docker inspect, docker exec, docker rm, docker network ls, docker network inspect, docker volume ls, docker volume inspect, docker compose up, and docker compose down. These cover the everyday tasks of starting test infrastructure, checking its state, investigating failures, and cleaning up environments.

Comments(0)

Submit a Comment

Your email address will not be published. Required fields are marked *

Top Picks For you

Talk to our Experts

Amazing clients who
trust us


poloatto
ABB
polaris
ooredo
stryker
mobility