How to Use Docker: Compose, Kubernetes & CI/CD for Beginners
Key Takeaways
- Docker containers run isolated apps with minimal overhead—use 10-50 MB images vs 1-10 GB VMs.
- Docker Compose simplifies multi-container setups with a single YAML file. Save hours per project.
- Kubernetes orchestrates containers at scale; start with Minikube for local testing.
- CI/CD pipelines with Docker cut deployment time by 40-60% in my experience.
---
Getting Started with Docker Containers
Docker packages your app and its dependencies into a lightweight container. Unlike virtual machines (VMs), containers share the host OS kernel, so they start in seconds, not minutes. For example, a simple Node.js app runs in a ~150 MB image vs a 2 GB VM.
Step 1: Install Docker
- Download from docker.com. On Ubuntu 22.04, I run `sudo apt install docker.io`.
- Verify with `docker --version`. You want 24.0 or later.
Step 2: Your First Container
```bash
docker run -d -p 8080:80 nginx:alpine
```
This pulls the Nginx image (22 MB compressed) and starts it on port 8080. Open http://localhost:8080—you’ll see the default page.
Step 3: Build Your Own Image
Create a `Dockerfile`:
```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
CMD ["node", "server.js"]
```
Build with `docker build -t myapp .` (takes ~30 seconds on a good connection).
---
Docker Compose for Multi-Container Apps
Single containers are fine for testing, but real apps need databases, caches, and queues. Docker Compose ties them together in one file.
Example: Node App + PostgreSQL
Create `docker-compose.yml`:
```yaml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
DB_HOST: db
db:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
```
Run `docker-compose up -d`. Both services start in under 5 seconds. I once spent 3 hours debugging a connection string—don’t forget `DB_HOST: db`, not `localhost`.
Why Compose?
- Single command for everything.
- Volume mounts preserve data across restarts.
- Easy to share setups with teammates.
---
Kubernetes Basics (Without the Panic)
Kubernetes (K8s) manages container clusters. It’s overkill for one app, but essential for scaling. Start locally with Minikube.
Step 1: Install Minikube
```bash
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
minikube start --driver=docker
```
This spins up a single-node cluster with 2 CPUs and 2 GB RAM.
Step 2: Deploy a Pod
A pod is the smallest K8s unit. Save as `pod.yaml`:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
```
Run `kubectl apply -f pod.yaml`. Check with `kubectl get pods`.
Comparison: Docker vs Kubernetes
| Feature | Docker Compose | Kubernetes |
| --------- | ---------------- | ------------ |
| Scale | Manual | Auto-scaling |
| Networking | Simple | Complex (Services, Ingress) |
| Persistence | Volumes | PersistentVolumeClaims |
| Learning curve | Low | High |
When to use K8s: You have >5 services or need zero-downtime deployments. For a side project, stick with Compose.
---
CI/CD Pipelines with Docker
Continuous Integration/Deployment (CI/CD) automates building and deploying containers. I use GitHub Actions for most projects.
Example: Deploy to Docker Hub
Create `.github/workflows/deploy.yml`:
```yaml
name: Docker CI/CD
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Log in to Docker Hub
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: Build and push
run: |
docker build -t myuser/myapp:latest .
docker push myuser/myapp:latest
```
Every push to `main` triggers a build. My team cut deployment time from 20 minutes to 8 minutes using this pattern.
Pro tip: Tag images with commit hashes (`docker build -t myapp:${{ github.sha }}`) for easy rollbacks.
Testing in CI
Before deploying, run tests inside the container:
```bash
docker run --rm myapp:latest npm test
```
If tests fail, the pipeline stops—no bad code shipped.
---
Common Mistakes to Avoid
1. Running containers as root – Use `USER node` in Dockerfile for security.
2. Ignoring .dockerignore – Add `node_modules` and `.git` to keep builds small. A 300 MB image takes 3x longer to push.
3. Forgetting to remove unused images – `docker system prune -a` frees gigabytes. I reclaim 5-10 GB monthly.
---
FAQ
Q: Do I need Kubernetes if I only have one service?
A: No. Docker Compose handles that fine. Kubernetes adds complexity without benefit for single-service apps. Use it when you need scaling or multiple microservices.
Q: How do I persist data in Docker containers?
A: Use volumes. In Compose, define a volume under `volumes:` and mount it with `- /path/in/container`. Data survives container restarts.
Q: Why is my container not connecting to the database?
A: Check networking. In Compose, services communicate by service name (e.g., `db`). In standalone Docker, use `--network` flag or `--link`. Also verify environment variables.
---
*Start small. Run one container. Add Compose. Explore Kubernetes when you hit limits. Docker isn’t magic—it’s just better packaging.*