Stackvora.tech

Docker for Beginners: Images, Containers, Volumes and Networks

Docker provides a lightweight, portable, and efficient method for packaging, distributing, and running applications. Understanding its core…

Docker provides a lightweight, portable, and efficient method for packaging, distributing, and running applications. Understanding its core components—images, containers, volumes, and networks—is fundamental to leveraging its full potential for local development, CI/CD pipelines, and production deployments.

Images vs. Containers: The Blueprint and the Instance

The distinction between Docker images and containers is central to Docker's operational model. An image is a read-only, layered template containing an application and all its dependencies, including the operating system, libraries, and application code. It's akin to a class in object-oriented programming or a virtual machine template. Images are built from a Dockerfile.

A container is a runnable instance of an image. When you run an image, Docker adds a thin, writable layer on top of the image's read-only layers. All changes made by the container (e.g., creating files, modifying data) are stored in this writable layer, keeping the underlying image pristine. Containers are isolated from each other and from the host system, ensuring consistent execution environments.

Building an Image with a Dockerfile

A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Here’s a basic example for a Node.js application:

# Use an official Node.js runtime as the base image
FROM node:20-alpine

# Set the working directory inside the container
WORKDIR /app

# Copy package.json and package-lock.json (if present) to the working directory
# Using a glob pattern ensures both are copied.
COPY package*.json ./

# Install application dependencies.
# Using 'npm ci' for clean installs in automated environments, '--omit=dev' for production.
RUN npm ci --omit=dev

# Copy the rest of the application source code
COPY . .

# Expose the port the application listens on
EXPOSE 3000

# Define the command to run the application when the container starts
CMD ["node", "server.js"]
  • FROM node:20-alpine: Specifies the base image. alpine variants are significantly smaller, reducing image size.
  • WORKDIR /app: Sets the current working directory for subsequent instructions.
  • COPY package*.json ./: Copies package definition files early to leverage Docker's build cache. If these files don't change, subsequent npm ci steps are skipped.
  • RUN npm ci --omit=dev: Installs dependencies. npm ci is preferred over npm install in automated builds for its determinism.
  • COPY . .: Copies the rest of the application code.
  • EXPOSE 3000: Informs Docker that the container listens on the specified network port at runtime. This doesn't actually publish the port.
  • CMD ["node", "server.js"]: Defines the default command to execute when a container starts from this image.

Creating and Managing Containers

Once an image is built, you can run it as a container. First, build the image:

docker build -t myapp:1.0 .

This command builds an image from the Dockerfile in the current directory and tags it as myapp:1.0. Then, run a container:

docker run -d --name api -p 3000:3000 myapp:1.0
  • -d: Runs the container in detached mode (in the background).
  • --name api: Assigns a human-readable name to the container (api).
  • -p 3000:3000: Maps host port 3000 to container port 3000. This is how external traffic can reach your application.
  • myapp:1.0: The image to use.

To inspect logs from a running container:

docker logs -f api

-f (follow) streams new logs as they are generated.

Data Persistence with Volumes

By default, data written inside a container's writable layer is ephemeral. If the container is removed, that data is lost. Docker volumes provide a mechanism for persisting data generated by and used by Docker containers. Volumes are the preferred way to persist data.

Types of Volumes

Docker offers two primary ways to persist data:

  1. Bind Mounts: Mounts a file or directory from the host machine into the container. The host path must exist. This is common for local development where source code is shared.
  2. Docker Volumes: Managed by Docker. Docker creates and manages the volume on the host system. They are more robust and portable for production environments as their location on the host is abstracted.

Using Docker Volumes

Create a named volume:

docker volume create appdata

Run a container, mounting the volume:

docker run -d --name api -p 3000:3000 -v appdata:/data myapp:1.0

Here, appdata is the named volume, and /data is the path inside the container where the volume will be mounted. Any data written to /data inside the container will be stored in the appdata volume on the host, persisting even if the api container is removed.

For a PostgreSQL database container, you'd typically mount a volume to the default data directory (/var/lib/postgresql/data):

docker run -d --name db \
    -e POSTGRES_PASSWORD=mysecretpassword \
    -v pgdata:/var/lib/postgresql/data \
    postgres:16

This ensures your database data persists across container restarts or removals.

Container Networking

Docker's networking model allows containers to communicate with each other and with the outside world. By default, containers are connected to a bridge network, allowing them to communicate via IP addresses. However, user-defined bridge networks offer superior isolation and service discovery.

User-Defined Bridge Networks

User-defined networks provide automatic DNS resolution for container names, making inter-container communication much simpler. Instead of relying on IP addresses, containers can refer to each other by their assigned names.

Create a user-defined bridge network:

docker network create app-net

Connect containers to this network:

# Run the PostgreSQL database container
docker run -d --name db \
    --network app-net \
    -e POSTGRES_PASSWORD=mysecretpassword \
    -v pgdata:/var/lib/postgresql/data \
    postgres:16

# Run the Node.js API container
docker run -d --name api \
    --network app-net \
    -p 3000:3000 \
    myapp:1.0

Within the api container, you can now connect to the database using the hostname db. For example, a Node.js application might use a connection string like postgres://user:mysecretpassword@db:5432/mydatabase. The Docker DNS resolver within app-net will resolve db to the IP address of the PostgreSQL container.

Network Drivers

Docker supports several network drivers:

Driver Use Case Description
bridge Default, single host Isolated networks for containers on a single host. User-defined bridges are recommended.
host Performance, bypass isolation Removes network isolation between container and host. Container uses host's network stack directly.
overlay Multi-host, Swarm Enables communication between containers running on different Docker hosts. Used by Docker Swarm.
macvlan Assign physical MAC/IP Assigns a MAC address to a container, making it appear as a physical device on the network.
none Complete network isolation Disables all networking for a container. Useful for batch jobs that don't need network access.

Container Lifecycle and Management

Understanding how to manage container lifecycles is crucial for day-to-day operations.

  • List running containers: docker ps
  • List all containers (including stopped): docker ps -a
  • Stop a container: docker stop [container_name_or_id]
  • Start a stopped container: docker start [container_name_or_id]
  • Restart a container: docker restart [container_name_or_id]
  • Remove a container: docker rm [container_name_or_id] (must be stopped first, or use -f to force)
  • Remove all stopped containers: docker container prune
  • Remove unused images: docker image prune (or docker image prune -a to remove all unused images, including dangling ones)
  • Execute a command inside a running container: docker exec -it [container_name_or_id] bash (-it for interactive pseudo-TTY)

Common Pitfalls and Troubleshooting

  • Port Conflicts: If docker run -p 3000:3000 fails, it means host port 3000 is already in use. Check with netstat -tuln | grep 3000 or choose a different host port.
  • Dockerfile Cache Invalidations: Docker builds images layer by layer. If a layer changes, all subsequent layers are rebuilt. Place frequently changing instructions (like COPY . .) later in the Dockerfile to maximize cache hits for stable dependencies.
  • Container Exits Immediately: Often due to the CMD or ENTRYPOINT instruction failing or completing its task. Use docker logs [container_name] to see the error. Sometimes, a process runs and exits, like a script, and the container stops because there's no ongoing process to keep it alive. Ensure your CMD runs a long-lived process (e.g., a web server).
  • Data Not Persisting: If data is lost after a container restart or removal, you're likely not using volumes correctly. Verify the volume is mounted to the correct path within the container. Use docker inspect [container_name] and check the "Mounts" section.
  • Inter-Container Communication Issues: If containers on a user-defined network can't communicate, double-check that they are both connected to the same user-defined network (docker network inspect [network_name]) and that you are using container names for DNS resolution, not IP addresses.
  • "No such image" or "No such container": Ensure correct spelling and case sensitivity for image tags and container names. Use docker images and docker ps -a to verify existence.

Back to the knowledge base · Ask the AI assistant