How to Use Docker: Compose, Kubernetes & CI/CD Tutorial for Beginners

2026-06-05·SaaS Setup

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

FeatureSingle ContainerDocker Compose

---------
Setup time5 minutes10 minutes
Multi-serviceManual linkingAutomatic networking
Data persistenceManual volume mountNamed volumes
Ideal forSimple apps, scriptsWeb 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.