Docker for Developers: Images, Containers, and Compose
As a developer, you've likely faced the frustration of environment inconsistencies: "It works on my machine!" Docker solves this by packaging applications into containers. This guide explains Docker images, containers, and Compose to help you build, ship, and run applications consistently.
What is Docker?
Docker is a platform for developing, shipping, and running applications in containers. Containers are lightweight, standalone, executable packages that include everything needed to run software: code, runtime, system tools, libraries, and settings.
Images vs. Containers
An image is a read-only template with instructions for creating a container. A container is a runnable instance of an image. You can create multiple containers from the same image, each isolated from the others.
Working with Images
Images are built from a Dockerfile, a text file with instructions. Here's a simple Node.js example:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Build the image with:
docker build -t my-app .
List images: docker images
Running Containers
Start a container from an image:
docker run -d -p 3000:3000 --name my-container my-app
This runs the container in detached mode (-d), maps port 3000, and names it my-container.
List running containers: docker ps
Stop a container: docker stop my-container
Remove a container: docker rm my-container
Why Use Docker Compose?
When your app depends on multiple services (database, cache, etc.), managing individual containers becomes tedious. Docker Compose lets you define and run multi-container applications using a YAML file.
Compose File Example
Create docker-compose.yml:
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: example
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Start all services: docker compose up -d
Stop and remove: docker compose down
Best Practices for Developers
- Use .dockerignore: Exclude unnecessary files (node_modules, .git) to speed up builds.
- Multi-stage builds: Keep production images small by separating build and runtime stages.
- Leverage caching: Order Dockerfile instructions from least to most frequently changing.
- Tag images properly: Avoid using
latestin production; use semantic versioning. - Scan for vulnerabilities: Use
docker scanto check images for known issues.
Common Commands Cheat Sheet
| Command | Description |
|---|---|
docker build -t name . | Build an image from Dockerfile |
docker run -p host:container image | Run a container and map ports |
docker ps -a | List all containers |
docker exec -it container bash | Open a shell inside a running container |
docker compose up | Start services defined in Compose file |
docker compose logs -f | Follow logs from all services |
FAQ
What is the difference between an image and a container?
An image is a static template; a container is a running instance of that image. You can create many containers from one image.
When should I use Docker Compose?
Use Compose when your application requires multiple services (e.g., web server, database, cache) that need to run together. It simplifies orchestration for development and testing.
How do I persist data in Docker?
Use volumes or bind mounts. Volumes are managed by Docker and stored in a dedicated area; bind mounts link to a host directory. Both survive container restarts.
Ready to streamline your Docker workflow? Try our Nginx Log Analyzer to debug container logs quickly.