Docker

build once, run anywhere. no more “works on my machine”.


what is Docker

Docker is a tool that packages your application and all its dependencies into a container — a lightweight, isolated environment that runs the same everywhere.

the problem it solves:

developer 1: "it works on my machine"
developer 2: "it crashes on mine"
production:  "completely broken"

reason: different OS, different Python version, different library versions,
        different environment variables, different system config

with Docker:

you define exactly what environment your app needs
Docker creates that environment everywhere identically
works the same on your laptop, teammate's laptop, server

containers vs virtual machines: - VM: full OS, heavy (GBs), slow to start (minutes) - Container: shares OS kernel, lightweight (MBs), starts in seconds


core concepts

image — blueprint for a container. built from a Dockerfile. like a class in OOP.

container — a running instance of an image. like an object (instance of a class).

Dockerfile — text file with instructions to build an image.

Docker Hub — registry of public images. like npm for Docker.

docker-compose — tool to define and run multi-container apps (frontend + backend + database).

Dockerfile → docker build → Image → docker run → Container

install Docker

# Ubuntu/Linux Lite
sudo apt update
sudo apt install docker.io docker-compose
sudo systemctl start docker
sudo systemctl enable docker

# add your user to docker group (no sudo needed)
sudo usermod -aG docker $USER
newgrp docker

# verify
docker --version
docker run hello-world

Dockerfile

a Dockerfile is a recipe for building an image.

# FastAPI backend Dockerfile
FROM python:3.11-slim

# set working directory inside container
WORKDIR /app

# copy requirements first (for layer caching)
COPY requirements.txt .

# install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# copy rest of code
COPY . .

# expose port
EXPOSE 8000

# command to run when container starts
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# React frontend Dockerfile (multi-stage build)
# stage 1: build
FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# stage 2: serve (much smaller final image)
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# Python script/tool Dockerfile
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

# for scripts (not servers)
ENTRYPOINT ["python", "main.py"]

Dockerfile instructions:

FROM image:tag        # base image to start from
WORKDIR /path         # set working directory
COPY src dest         # copy files from host to image
RUN command           # run command during build
ENV KEY=VALUE         # set environment variable
EXPOSE 8080           # document which port to expose
CMD ["cmd", "arg"]    # default command when container starts
ENTRYPOINT ["cmd"]    # main command (CMD provides default args)
ARG VAR=default       # build-time variable
VOLUME /path          # mount point for external storage

building and running images

# build image from Dockerfile in current directory
docker build -t my-app .
docker build -t my-app:v1.0 .        # with tag
docker build -t my-app -f Dockerfile.prod .  # specific Dockerfile

# run container
docker run my-app
docker run -p 8000:8000 my-app       # map host:container port
docker run -p 3000:80 my-app         # host 3000 → container 80
docker run -d my-app                 # detached (background)
docker run -d --name my-api my-app   # with name
docker run -it my-app bash           # interactive shell

# environment variables
docker run -e DATABASE_URL=postgres://... my-app
docker run --env-file .env my-app

# volumes (persist data)
docker run -v /host/path:/container/path my-app
docker run -v $(pwd):/app my-app     # mount current directory

# full example
docker run -d \
    --name studentos-api \
    -p 8000:8000 \
    --env-file .env \
    -v $(pwd)/uploads:/app/uploads \
    my-app

managing containers

# list
docker ps              # running containers
docker ps -a           # all containers including stopped

# stop/start/restart
docker stop container_name
docker start container_name
docker restart container_name

# remove
docker rm container_name          # remove stopped container
docker rm -f container_name       # force remove running container

# logs
docker logs container_name
docker logs -f container_name     # follow (live)
docker logs --tail 50 container_name  # last 50 lines

# execute command in running container
docker exec -it container_name bash   # open shell
docker exec container_name ls -la     # run command

# copy files
docker cp container_name:/app/file.txt .   # container to host
docker cp file.txt container_name:/app/    # host to container

# inspect
docker inspect container_name     # detailed info
docker stats                       # live resource usage

managing images

# list images
docker images
docker image ls

# remove image
docker rmi image_name
docker rmi image_name:tag
docker image prune           # remove unused images

# pull from Docker Hub
docker pull python:3.11
docker pull postgres:16
docker pull nginx:alpine

# push to Docker Hub
docker login
docker tag my-app username/my-app:v1.0
docker push username/my-app:v1.0

# view image layers
docker history image_name

docker-compose

docker-compose defines multi-container applications in one file. instead of running multiple docker run commands, you run one command.

# docker-compose.yml
version: "3.9"

services:

  # FastAPI backend
  api:
    build: ./backend
    container_name: studentos-api
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://postgres:password@db:5432/studentos
      - JWT_SECRET=${JWT_SECRET}
    env_file:
      - ./backend/.env
    volumes:
      - ./backend:/app    # mount code for hot reload
    depends_on:
      - db
    restart: unless-stopped

  # React frontend (development)
  frontend:
    build: ./frontend
    container_name: studentos-frontend
    ports:
      - "5173:5173"
    volumes:
      - ./frontend:/app
      - /app/node_modules   # don't override container's node_modules
    environment:
      - VITE_API_URL=http://localhost:8000
    depends_on:
      - api

  # PostgreSQL database
  db:
    image: postgres:16-alpine
    container_name: studentos-db
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_DB=studentos
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data   # persist data
    restart: unless-stopped

  # Redis cache
  redis:
    image: redis:7-alpine
    container_name: studentos-redis
    ports:
      - "6379:6379"
    restart: unless-stopped

volumes:
  postgres_data:    # named volume for database data
# start everything
docker-compose up
docker-compose up -d         # detached
docker-compose up --build    # rebuild images first

# stop everything
docker-compose down
docker-compose down -v       # also remove volumes (deletes data)

# logs
docker-compose logs
docker-compose logs api      # specific service
docker-compose logs -f       # follow

# restart specific service
docker-compose restart api

# run command in service
docker-compose exec api bash
docker-compose exec db psql -U postgres

# rebuild specific service
docker-compose up -d --build api

.dockerignore

like .gitignore — tells Docker what NOT to copy into the image.

node_modules
.git
.env
.env.local
dist
build
__pycache__
*.pyc
.pytest_cache
*.log
.DS_Store

real world project structure

my-project/
├── docker-compose.yml          # development
├── docker-compose.prod.yml     # production
├── frontend/
│   ├── Dockerfile
│   ├── Dockerfile.prod
│   ├── .dockerignore
│   ├── nginx.conf
│   └── src/
├── backend/
│   ├── Dockerfile
│   ├── .dockerignore
│   ├── requirements.txt
│   └── main.py
└── .env                        # shared environment variables

useful Docker commands

# system cleanup
docker system prune              # remove stopped containers, unused images
docker system prune -a           # also remove all unused images
docker volume prune              # remove unused volumes

# disk usage
docker system df

# network
docker network ls
docker network create my-network
docker run --network my-network my-app

# pull, build and push shortcut
docker buildx build --platform linux/amd64,linux/arm64 -t username/app:latest --push .

Docker for your projects

StudentOS local development:

# no more manual setup for new teammates
git clone repo
docker-compose up
# app runs at localhost:5173, api at localhost:8000

knowledge hub:

services:
  web:
    build: .
    ports: ["3000:3000"]
  db:
    image: postgres:16-alpine

private chat app:

services:
  app:
    build: .
  db:
    image: postgres:16-alpine
  redis:
    image: redis:7-alpine  # for WebSocket sessions

=^._.^= if it runs in Docker, it runs everywhere