How to Use Docker: Compose, Kubernetes, and CI/CD for Beginners
Key Takeaways
- Docker packages apps with dependencies into lightweight containers, ensuring consistency across environments.
- Docker Compose lets you define and run multi-container apps with a single YAML file—perfect for local dev.
- Kubernetes automates deployment, scaling, and management of containers in production, but start small to avoid complexity.
- CI/CD pipelines using Docker images cut deployment time by 60-80% compared to traditional methods.
---
# How to Use Docker: Compose, Kubernetes, and CI/CD for Beginners
I remember my first encounter with Docker in 2018. I was building a Python web app with PostgreSQL and Redis, and every teammate had a different way of setting it up. One had Python 3.6, another 3.8, and the Redis version varied. Hours were lost debugging "works on my machine" issues. Docker fixed that.
In this guide, I'll walk you through the practical steps to use Docker for containerization, Docker Compose for multi-service apps, Kubernetes basics for orchestration, and how to integrate everything into a CI/CD pipeline. No fluff—just real examples.
What Is Docker and Why Should You Care?
Docker is a platform that runs applications in isolated environments called containers. Each container includes the app code, runtime, system tools, and libraries. Unlike virtual machines (VMs) that emulate entire operating systems, containers share the host OS kernel, making them lightweight.
Real numbers:
- A typical container starts in under 1 second, while a VM takes 30-60 seconds.
- Containers use 50-80% less memory than equivalent VMs.
- Docker Hub hosts over 8 million container images as of 2024.
Step 1: Install Docker and Run Your First Container
First, download Docker Desktop from [docker.com](https://docker.com) for Windows or macOS. On Linux, use your package manager. For Ubuntu:
```bash
sudo apt update
sudo apt install docker.io
sudo systemctl start docker
sudo systemctl enable docker
```
Verify installation:
```bash
docker --version
# Output: Docker version 24.0.7, build afdd53b
```
Now run a simple container:
```bash
docker run hello-world
```
You'll see a message confirming Docker works. This command pulls the `hello-world` image from Docker Hub and runs it in a container that exits after printing.
Step 2: Containerize Your Own App
Let's containerize a simple Node.js app. Create a `package.json` and `server.js`:
```javascript
// server.js
const http = require('http');
const hostname = '0.0.0.0';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello from Docker!\n');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
```
Create a `Dockerfile` in the same directory:
```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
```
Build the image:
```bash
docker build -t my-node-app .
```
Run it:
```bash
docker run -p 3000:3000 my-node-app
```
Visit `http://localhost:3000`—you'll see the message. The `-p` flag maps port 3000 on your host to port 3000 in the container.
Step 3: Use Docker Compose for Multi-Service Apps
Single containers are fine for simple apps, but real projects have multiple services (e.g., web, database, cache). Docker Compose lets you define them in a `docker-compose.yml` file.
Create a `docker-compose.yml` for a web app with PostgreSQL:
```yaml
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-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
```
Run everything with one command:
```bash
docker-compose up -d
```
The `-d` flag runs containers in detached mode. Stop with `docker-compose down`. This is my go-to for local development because it replicates production environments.
Step 4: Kubernetes Basics for Orchestration
Docker Compose is great for single-machine setups, but when you need to scale across multiple servers, Kubernetes (K8s) takes over. K8s automates deployment, scaling, and management of containers.
Key concepts:
- Pod: Smallest deployable unit—runs one or more containers.
- Deployment: Manages replicas of pods.
- Service: Exposes pods internally or externally.
Here's a basic deployment for our Node.js app:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-app
spec:
replicas: 3
selector:
matchLabels:
app: node-app
template:
metadata:
labels:
app: node-app
spec:
containers:
- name: node-app
image: my-node-app:latest
ports:
- containerPort: 3000
```
Apply it with `kubectl apply -f deployment.yaml`. Then create a service to expose it:
```yaml
apiVersion: v1
kind: Service
metadata:
name: node-app-service
spec:
selector:
app: node-app
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
```
Apply with `kubectl apply -f service.yaml`. Kubernetes will distribute traffic across your three pods.
Comparison: Docker Compose vs. Kubernetes
| Feature | Docker Compose | Kubernetes |
| ------------------------ | --------------------------------- | -------------------------------- |
| Best for | Local dev, single-machine | Production, multi-machine |
| Setup complexity | Low (one YAML file) | High (cluster setup needed) |
| Scaling | Manual (`--scale`) | Automatic (HPA) |
| Resource management | Basic | Advanced (CPU/memory limits) |
| Learning curve | Gentle | Steep |
Start with Compose for learning and small projects; move to K8s when you need high availability.
Step 5: Integrate Docker with CI/CD Pipelines
Continuous Integration/Continuous Deployment (CI/CD) pipelines automate building, testing, and deploying Docker images. Here's a practical example using GitHub Actions.
Create `.github/workflows/docker-deploy.yml`:
```yaml
name: Build and Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
push: true
tags: yourusername/my-app:latest
- name: Deploy to server
run: |
ssh user@server "docker pull yourusername/my-app:latest && docker-compose up -d"
```
This pipeline triggers on every push to `main`. It builds the image, pushes it to Docker Hub, then SSHes into your server to redeploy. My team cut deployment time from 15 minutes to under 2 using this approach.
Common Pitfalls to Avoid
- Ignoring `.dockerignore`: Include node_modules, .git, and other unnecessary files to keep images small.
- Using `latest` tag in production: Pin versions (e.g., `node:18-alpine`) for reproducibility.
- Running containers as root: Use `USER` in Dockerfile to run as non-root for security.
FAQ
Q: Do I need to learn Docker before Kubernetes?
Yes, absolutely. Kubernetes manages containers, so you need to understand how to build and run Docker images first. Master Docker Compose before touching K8s—it builds foundational skills.
Q: Can I use Docker on Windows without WSL?
Docker Desktop on Windows now requires WSL 2 (Windows Subsystem for Linux). It's free and easy to install via PowerShell: `wsl --install`. The performance is excellent—I've run production-like stacks without issues.
Q: How do I persist data in Docker containers?
Use volumes or bind mounts. Volumes are managed by Docker (e.g., `docker volume create mydata`) and survive container restarts. Bind mounts link a host directory to a container path—handy for development because changes reflect immediately.
---
Docker isn't just a tool—it's a mindset shift. Once you containerize, you'll wonder how you lived without it. Start with the basics, experiment with Compose, and gradually explore Kubernetes. The learning curve is worth it.
If you hit a wall, the Docker documentation is excellent, and the community on Stack Overflow is active. Happy containerizing!