Docker for Developers: Images, Containers, and Compose
You've built an app that works on your machine, but when you deploy it, things break. Dependencies are missing, versions differ, and configuration is a mess. Docker solves this by packaging your application and its environment into a single, portable unit. This article explains Docker images, containers, and Docker Compose with practical examples so you can streamline your development workflow.
What is a Docker Image?
A Docker image is a read-only template that contains your application code, runtime, libraries, environment variables, and configuration files. Think of it as a snapshot of your app's environment. Images are built from a Dockerfile, which is a text file with instructions.
Here's a simple Dockerfile for a Node.js app:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Each instruction creates a layer. Layers are cached, so rebuilding after a code change only reruns steps from the changed layer onward. This makes builds fast.
What is a Docker Container?
A container is a running instance of an image. It's an isolated process with its own filesystem, network, and process space, but it shares the host OS kernel. This makes containers lightweight and fast to start compared to virtual machines.
To run a container from an image:
docker run -d -p 3000:3000 --name myapp my-node-app
This starts a container in detached mode (-d), maps port 3000 on the host to port 3000 in the container (-p 3000:3000), and names it myapp.
Containers are ephemeral by default. Any data written inside is lost when the container stops. To persist data, use volumes:
docker run -v /host/data:/app/data my-node-app
Why Use Docker Compose?
Real applications often need multiple services: a web server, a database, a cache, etc. Running each with docker run and linking them manually is tedious. Docker Compose lets you define and run multi-container apps with a single YAML file.
Here's a docker-compose.yml for a Node.js app with PostgreSQL and Redis:
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
db:
image: postgres:15
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- pgdata:/var/lib/postgresql/data
cache:
image: redis:7
volumes:
pgdata:
Run everything with:
docker compose up -d
Compose creates a network so services can reach each other by name (e.g., db, cache). It also handles volume creation and dependency order.
Key Docker Commands for Developers
docker build -t myapp .– Build an image from a Dockerfile in the current directory.docker images– List local images.docker ps– List running containers (add-afor all).docker exec -it myapp sh– Open a shell inside a running container.docker logs myapp– View container logs.docker compose down– Stop and remove containers, networks, and volumes defined in Compose.
Best Practices for Docker in Development
- Use .dockerignore to exclude node_modules, .git, and other unnecessary files from the build context.
- Leverage multi-stage builds to keep production images small. For example, build your app in one stage and copy only the artifacts to a minimal runtime image.
- Pin versions in your Dockerfile (e.g.,
node:18-alpineinstead ofnode:latest) for reproducible builds. - Use volumes for code during development for hot reloading:
volumes: - .:/appin Compose. - Don't run as root in production. Create a non-root user in your Dockerfile.
Common Pitfalls and How to Avoid Them
Pitfall 1: Bloated images. Using a full OS base image like ubuntu can result in 1GB+ images. Use slim or alpine variants.
Pitfall 2: Slow builds. Order Dockerfile instructions from least to most frequently changing. Copy package files and install dependencies before copying source code.
Pitfall 3: Data loss. Always use volumes for databases. Without them, data disappears when the container is removed.
Pitfall 4: Networking confusion. In Compose, services communicate over a default network using service names as hostnames. Don't use localhost to reach another service.
FAQ
What's the difference between an image and a container?
An image is a read-only template; a container is a running instance of that image. You can run multiple containers from the same image.
Can I use Docker Compose in production?
Compose is primarily for development and testing. For production, consider orchestrators like Kubernetes or Docker Swarm, though Compose can work for simple deployments.
How do I update a container with new code?
Rebuild the image with docker build and then recreate the container: docker compose up -d --build.
Ready to streamline your development workflow? Try our Nginx Log Analyzer to debug your containerized web server logs quickly.