# Docker & Docker Compose Cheatsheet

For L13 and Lab 8 — containerise, run, and ship a multi-service app.

## Core concepts

- **Image** — read-only template (e.g. `node:20`).
- **Container** — running instance of an image.
- **Volume** — persistent storage outside the container.
- **Network** — virtual network letting containers talk to each other.

## Daily commands

```bash
docker --version
docker info
docker ps                       # running containers
docker ps -a                    # all containers (incl. stopped)
docker images                   # local images
docker pull node:20             # download an image
docker run -it --rm node:20 bash       # interactive throwaway shell
docker run -d -p 3000:3000 my-app      # detached, port mapping
docker stop <id>                # graceful stop
docker rm   <id>                # remove container
docker rmi  <image>             # remove image
docker logs -f <id>             # tail logs
docker exec -it <id> sh         # shell into running container
```

## Cleanup

```bash
docker container prune          # remove stopped containers
docker image prune              # remove dangling images
docker system prune -a          # nuclear: removes unused images too
docker volume ls
docker volume prune
```

## Dockerfile (Node example)

```dockerfile
# syntax=docker/dockerfile:1
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN  npm ci
COPY . .
RUN  npm run build

FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./
EXPOSE 3000
CMD ["node", "dist/server.js"]
```

### Dockerfile (Spring Boot)

```dockerfile
FROM eclipse-temurin:21-jdk AS build
WORKDIR /src
COPY . .
RUN ./mvnw -DskipTests package

FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /src/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
```

## Build & run

```bash
docker build -t my-app:1.0 .
docker run -d -p 8080:8080 --name api my-app:1.0
```

## docker-compose.yml

```yaml
services:
  web:
    image: nginx:alpine
    ports: ["80:80"]
    depends_on: [api]

  api:
    build: ./api
    environment:
      DATABASE_URL: postgres://app:secret@db:5432/app
    ports: ["8080:8080"]
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: app
    volumes: [pgdata:/var/lib/postgresql/data]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 5s
      retries: 10

volumes:
  pgdata:
```

```bash
docker compose up -d            # start in background
docker compose ps               # status
docker compose logs -f api      # follow one service
docker compose exec db psql -U app
docker compose down             # stop + remove
docker compose down -v          # ...and remove volumes (data loss!)
```

## Best practices

- Use small base images (`alpine`, `slim`).
- Multi-stage builds — keep build tools out of the runtime image.
- Order Dockerfile lines from least to most frequently changed (better cache).
- Use `.dockerignore` (similar to `.gitignore`) to keep images small.
- Never bake secrets into images — use env vars or secrets managers.
- Run as a non-root user for production images.

## .dockerignore

```
node_modules
.git
.env
*.log
dist
target
__pycache__
```

## Tools & references

- Docker docs: <https://docs.docker.com/>
- Docker Hub: <https://hub.docker.com/>
- Compose spec: <https://docs.docker.com/compose/compose-file/>
- Play with Docker (browser sandbox): <https://labs.play-with-docker.com/>
