How to Use Docker: Compose, Kubernetes & CI/CD Tutorial for Beginners
Key Takeaways
- Docker packages your app and its dependencies into a lightweight container, ensuring it runs identically on any machine.
- Docker Compose lets you define and run multi-container applications with a single YAML file, perfect for local development.
- Kubernetes orchestrates containers at scale, but you don't need it for small projects—start with Docker Compose.
- A basic CI/CD pipeline using Docker can cut deployment time from hours to under 10 minutes.
What You'll Need to Follow Along
Before we dive in, make sure you have:
- Docker installed (version 24.0 or later). I’m using Docker Desktop 4.27 on Windows 11, but Linux or Mac works the same.
- A code editor (VS Code is fine).
- Basic familiarity with the command line.
I’ll walk through a real example: a simple Node.js app connected to a PostgreSQL database. You can grab the code from [my GitHub repo](https://github.com/example/docker-tutorial) if you want to copy-paste.
Step 1: Dockerize a Single App
Let’s start small. Create a folder called `myapp` and add a `Dockerfile` (no extension):
```dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]
```
This does four things:
1. Uses a minimal Node.js image (only 126 MB vs. 1.2 GB for a full OS).
2. Sets the working directory.
3. Installs production dependencies only—`npm ci` is faster than `npm install` and locks versions.
4. Copies your code and starts the app.
Build the image: `docker build -t myapp:1.0 .`
Run it: `docker run -p 3000:3000 myapp:1.0`
Now open `http://localhost:3000`. You should see your app. That’s it—your app is containerized.
Why This Matters
I once spent a whole afternoon debugging a "works on my machine" issue because one teammate had Node 18 and another had Node 20. Docker eliminates that. The image is a snapshot of the exact environment.
Step 2: Use Docker Compose for Multi-Container Apps
A single container is fine for a static site, but most apps need a database. Let’s add PostgreSQL.
Create a `docker-compose.yml` in the same folder:
```yaml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DB_HOST=db
- DB_USER=postgres
- DB_PASSWORD=secretpass
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: secretpass
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
```
Run everything with one command: `docker-compose up -d`
The `-d` flag runs containers in the background. Your app connects to the database using `db` as hostname—Docker’s internal DNS resolves service names.
Pro Tip
Use a named volume (`pgdata`) so your database survives container restarts. Without it, `docker-compose down` wipes all data. I learned this the hard way after losing a week’s worth of test data.
Comparison: Single Container vs. Docker Compose
| Feature | Single Container | Docker Compose |
| --- | --- | --- |
| Setup time | 5 minutes | 10 minutes |
| Multi-service | Manual linking | Automatic networking |
| Data persistence | Manual volume mount | Named volumes |
| Ideal for | Simple apps, scripts | Web apps with databases |
Step 3: Kubernetes Basics (When You Grow)
Once your app needs to handle thousands of users, you’ll want Kubernetes. But don’t jump to it too early—I see teams waste weeks on K8s for a 50-user app.
Kubernetes (K8s) runs containers across multiple machines. Here’s the minimal setup for our app:
Create a `deployment.yaml`:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:1.0
ports:
- containerPort: 3000
```
Apply it: `kubectl apply -f deployment.yaml`
This keeps 3 copies of your container running. If one crashes, Kubernetes starts a new one automatically. To expose it to the internet, you need a Service, but that’s a topic for another day.
When to Use Kubernetes
- You have more than 5 microservices.
- You need auto-scaling (e.g., Black Friday traffic).
- Your team has a dedicated DevOps person.
Otherwise, stick with Docker Compose. It’s simpler and costs $0.
Step 4: CI/CD Pipeline with Docker
Let’s automate deployment. I’ll show a GitHub Actions pipeline that builds, tests, and pushes your Docker image to Docker Hub.
Create `.github/workflows/deploy.yml`:
```yaml
name: Build and Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Run tests
run: docker run myapp:${{ github.sha }} npm test
- name: Push to Docker Hub
run: |
docker tag myapp:${{ github.sha }} yourusername/myapp:latest
docker push yourusername/myapp:latest
```
Every time you push to `main`, this pipeline:
1. Builds the image with a unique tag (the commit SHA).
2. Runs your tests inside the container.
3. Pushes the image to Docker Hub.
From there, you can pull the latest image on your server and restart containers. Manual deployment used to take me 30 minutes. Now it’s 4 minutes.
Common Mistakes to Avoid
- Ignoring `.dockerignore`: If you don’t exclude `node_modules`, your image will be huge. Add `node_modules` and `.git` to `.dockerignore`.
- Running as root: Containers run as root by default. Add `USER node` in your Dockerfile for security.
- Hardcoding secrets: Never put passwords in the Dockerfile. Use environment variables or Docker secrets.
FAQ
Q: Do I need to learn Kubernetes to use Docker?
No. Start with Docker Compose. Kubernetes is only necessary when you need to manage containers across multiple servers. For a single server, Compose works fine.
Q: How do I update a running container with Docker Compose?
Run `docker-compose pull` to get the latest image, then `docker-compose up -d`. Compose will recreate containers that changed. Your data persists in volumes.
Q: Can I use Docker for production on a $5 VPS?
Yes, but limit memory. Add `--memory=512m` to your run command. I run a small Rails app with PostgreSQL on a $10 DigitalOcean droplet using Compose, handling 500 daily users without issues.