Docker for Developers: Images, Containers, and Compose
You've written code that works on your machine, but when a teammate tries to run it, they hit missing dependencies, version conflicts, or obscure errors. Docker solves this by packaging your application and its environment into a single, portable unit. In this guide, you'll learn Docker fundamentals: images, containers, and Compose, with practical steps to containerize a simple web app.
What is Docker?
Docker is a platform for building, running, and sharing applications in containers. A container is a lightweight, standalone executable package that includes everything needed to run software: code, runtime, system tools, libraries, and settings. Containers isolate applications from each other and from the host system, ensuring consistency across development, testing, and production.
Images vs Containers: The Blueprint and the Running Instance
An image is a read-only template that defines a container. It's like a class in object-oriented programming. A container is a runnable instance of an image, like an object. You can create many containers from the same image.
Images are built from a Dockerfile, a text file with instructions. Containers are started with docker run.
Key differences
| Image | Container |
|---|---|
| Read-only template | Runnable instance |
Built with docker build | Started with docker run |
| Stored in registry (e.g., Docker Hub) | Runs on host |
| Layered filesystem | Writable layer on top |
Building Your First Image
Let's containerize a simple Node.js app. Create a file named Dockerfile:
# Use an official Node.js runtime as a parent image
FROM node:18-alpine
# Set the working directory
WORKDIR /app
# Copy package.json and install dependencies
COPY package*.json ./
RUN npm install
# Copy the rest of the application
COPY . .
# Expose the port the app runs on
EXPOSE 3000
# Define the command to run the app
CMD ["node", "server.js"]
Build the image with a tag:
docker build -t my-node-app .
The -t flag names and tags the image. The . at the end specifies the build context (current directory).
Running Containers
Start a container from your image:
docker run -p 3000:3000 -d --name my-app my-node-app
-p 3000:3000: Maps port 3000 on the host to port 3000 in the container.-d: Runs the container in detached mode (in the background).--name my-app: Assigns a name to the container for easy reference.
Check running containers with docker ps. Stop the container with docker stop my-app.
Managing Data with Volumes
Containers are ephemeral; data inside them is lost when the container is removed. To persist data, use volumes. For example, to persist database data:
docker run -v my-db-data:/var/lib/mysql -d mysql:8
This creates a named volume my-db-data that survives container restarts and removals.
Simplifying Multi-Container Apps with Docker Compose
Real applications often need multiple services: a web server, a database, a cache, etc. Docker Compose lets you define and run multi-container apps with a single YAML file.
Create a docker-compose.yml:
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
depends_on:
- db
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
db:
image: postgres:15
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=mydb
volumes:
postgres_data:
Run everything with:
docker compose up -d
Compose builds the web image, starts the database, and connects them on a shared network. The depends_on ensures the database starts before the web service.
Essential Docker Commands
docker ps -a: List all containers (including stopped).docker images: List images.docker logs my-app: View container logs.docker exec -it my-app sh: Open a shell inside a running container.docker compose down: Stop and remove containers, networks, and volumes defined in Compose file.
Best Practices for Development
- Use .dockerignore to exclude files (e.g., node_modules) from the build context, speeding up builds.
- Leverage multi-stage builds to keep production images small. For example, build your app in one stage and copy only the necessary artifacts to a slim runtime image.
- Pin versions in your Dockerfile (e.g.,
node:18-alpineinstead ofnode:latest) for reproducibility. - Run as non-root user for security. Add
USER nodeafter copying files.
FAQ
What is the difference between a Docker image and a container?
An image is a read-only template that defines the application and its environment. A container is a running instance of that image. You can create multiple containers from the same image.
How do I persist data in Docker?
Use volumes. Named volumes (e.g., docker run -v my-vol:/data) are stored outside the container's filesystem and persist across container restarts and removals.
When should I use Docker Compose?
Use Compose when your application consists of multiple services (e.g., web + database) that need to run together. It simplifies orchestration for development and testing.
Ready to containerize your next project? Start by writing a simple Dockerfile and running your first container. For more developer tools, check out TryQuickToolBox's JSON Formatter to validate API responses while testing your containerized services.