Docker container guide — short, practical, and honest. If you’re new to containerization or moving from VMs, this article explains what Docker containers are, how they work, and how to use them day-to-day. I’ll share hands-on examples, common pitfalls I’ve seen, and best practices that actually matter. Read this and you’ll be able to build images, run services with Docker Compose, push to Docker Hub, and think about orchestration with Kubernetes.
What is Docker and why it matters
At its core, Docker packages software into units called containers that run reliably across environments. Containers are lightweight compared to VMs because they share the host OS kernel while isolating processes and file systems. For a quick background, see the Docker Wikipedia page for history and context.
Core concepts: image, container, Dockerfile, Docker Hub
Images vs. containers
An image is a read-only template (think: a snapshot of a filesystem + metadata). A container is a running instance of that image with ephemeral state. Stop the container, and unless you persisted data, it’s gone. In my experience, thinking in terms of immutable images helps avoid messy production surprises.
Dockerfile basics
A Dockerfile is a small, version-controlled script that builds an image. Minimal example (build a simple Node app):
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install –production
COPY . .
CMD [“node”, “server.js”]
Use a slim base image and layer ordering to speed builds and reduce image size.
Docker Hub and registries
Docker Hub is the default public registry where you push and pull images. For private or enterprise needs, use a private registry or services like GitHub Container Registry. Official Docker docs explain registry workflows well: Docker Documentation.
Getting started: install, first container, Docker Compose
Install Docker Desktop on macOS/Windows or Docker Engine on Linux. Follow platform-specific steps on the official docs to avoid permission issues.
Your first commands
Try this sequence locally to verify everything works:
docker run –rm hello-world
docker pull nginx:latest
docker run -d -p 8080:80 –name web nginx:latest
That last command runs nginx on port 8080. Check http://localhost:8080.
Docker Compose for multi-service apps
When your app has multiple services (web, db, cache), use Docker Compose. Example docker-compose.yml (very simple):
version: ‘3.8’
services:
web:
build: ./web
ports:
– ‘8080:80’
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: example
Compose streamlines local dev, testing, and CI steps. It’s my go-to for reproducible local environments.
Practical examples and workflows
Build, tag, push
Typical image lifecycle:
- docker build -t yourname/app:1.0 .
- docker tag yourname/app:1.0 yourrepo/yourname/app:1.0
- docker push yourrepo/yourname/app:1.0
Automate these steps in CI. Keep tags immutable (use commit SHA) and a latest tag for convenience.
Persisting data
Containers are ephemeral. Use volumes for durable data:
docker run -d -v mydata:/var/lib/postgresql/data postgres:15
Named volumes are easier to manage than host mounts for portability.
Working with orchestration: when to consider Kubernetes
If you need automated scaling, self-healing, or rolling updates across many nodes, orchestration matters. Kubernetes is the industry standard. It adds complexity, so weigh operational overhead. For introductions and setup guides, the Kubernetes docs are extensive (searchable and official).
Best practices I actually use
- Small images: Use minimal base images (alpine or distroless) to reduce attack surface and faster pulls.
- Multi-stage builds: Build artifacts in one stage, copy only what’s needed into the runtime stage.
- Immutable tags: Use commit-based tags for deployments; avoid relying only on latest.
- Scan images: Integrate vulnerability scanning into CI before pushing to registries.
- Limit container privileges: Run as non-root when possible and drop unnecessary capabilities.
Common troubleshooting tips
- If containers fail to start, check logs: docker logs CONTAINER.
- Use docker ps -a to see exited containers and inspect exit codes.
- Networking issues? Verify port mappings and firewall rules. For Compose, check service health and depends_on semantics.
- Image build slow? Use cache wisely, reorder Dockerfile steps, and use .dockerignore to skip unnecessary files.
Real-world example: migrating a small Rails app
I migrated a small Rails app by creating a Dockerfile, using a multi-stage build, and a docker-compose stack including Postgres and Redis. The team gained faster onboarding (no more “works on my machine”), but we also learned the hard way about proper logging — set stdout/stderr to avoid losing logs to local files.
Resources and further reading
Official docs and trustworthy references are worth bookmarking: the Docker Documentation is the primary reference for commands and concepts. For a historical overview and additional links, check the Docker Wikipedia entry. If you want to publish images, explore Docker Hub for registry workflows.
Next steps: Build a simple image, push it to a registry, and run it on another machine or CI. Then add Compose and test a multi-service setup. From what I’ve seen, iterative practice is the fastest path to confidence.
Frequently asked questions
Note: See the FAQ section below for quick answers to common questions.
Frequently Asked Questions
A Docker container is a lightweight, standalone executable package that includes software and all its dependencies, sharing the host OS kernel while running isolated processes.
Write a Dockerfile that defines base image, files, dependencies, and commands, then run: docker build -t yourname/app:tag .
Use Docker Compose for local development and testing when your application has multiple services like web, database, and cache that must run together.
Persist data using Docker volumes or bind mounts. Named volumes are portable and easier to manage across environments.
Not always. Kubernetes is useful for production-level orchestration (scaling, self-healing). For single-node or small deployments, Docker and Compose may be sufficient.