Mini-Project 1 — “Hello static site”
Goal: create a tiny static site, containerise it with nginx, run it, push image to Docker Hub.
- Create project dir:
mkdir hello-static && cd hello-static
index.html:
<!-- index.html -->
<!doctype html>
<html><body><h1>Hello from Docker — static site</h1></body></html>
Dockerfile(minimal, production style):
# Dockerfile
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
EXPOSE 80
- Build and run:
docker build -t yourhubusername/hello-static:1.0 .
docker run -d --name hello -p 8080:80 yourhubusername/hello-static:1.0
# visit http://localhost:8080
- Push to Docker Hub:
docker push yourhubusername/hello-static:1.0
Explanation: nginx:alpine is a tiny base image; COPY places your static file into nginx’s default web root. Expose is informative for humans and some tools.
Here’s the clean and complete set of commands to stop, delete the container, and delete the image so you can rerun everything from scratch.
✅ 1. Stop the container
Since your container name is hello:
docker stop hello
This gracefully stops the running container.
✅ 2. Delete (remove) the container
docker rm hello
If the container is still running and you want to force remove:
docker rm -f hello
✅ 3. List images (optional)
docker images
This helps you confirm the image name and tag.
✅ 4. Delete the image
Your image name is:
yourhubusername/hello-static:1.0
So delete it using:
docker rmi yourhubusername/hello-static:1.0
If the image is used by a stopped container (rare case), force remove:
docker rmi -f yourhubusername/hello-static:1.0
🔄 Now you can rebuild and rerun the container from scratch
Build again:
docker build -t yourhubusername/hello-static:1.0 .
Run again:
docker run -d --name hello -p 8080:80 yourhubusername/hello-static:1.0
Mini-Project 2 — “Python Flask + PostgreSQL” (multi-container using Docker Compose)
Goal: show Compose, networking, persisted database volume.
- Project layout:
flask-postgres/
app/
app.py
requirements.txt
docker-compose.yml
Dockerfile
app/app.py(simple):
from flask import Flask
import psycopg2, os
app = Flask(__name__)
def get_db():
return psycopg2.connect(
host=os.environ.get("POSTGRES_HOST","db"),
dbname="demo", user="demo", password="demopass"
)
@app.route("/")
def home():
cur = get_db().cursor()
cur.execute("SELECT 1")
return "flask -> postgres ok"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
app/requirements.txt:
Flask
psycopg2-binary
Dockerfile(for Flask):
FROM python:3.11-slim
WORKDIR /app
COPY app/requirements.txt .
RUN pip install -r requirements.txt
COPY app/ .
EXPOSE 5000
CMD ["python","app.py"]
docker-compose.yml:
version: "3.8"
services:
web:
build: .
ports: ["5000:5000"]
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_DB: demo
POSTGRES_USER: demo
POSTGRES_PASSWORD: demopass
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Explanation: This Compose file starts two services: a Flask app and a PostgreSQL database.
The Flask app is built from the local Dockerfile, and it exposes port 5000 to my machine.
PostgreSQL runs with a default user, password, and database, and stores data in a persistent volume so it doesn’t get lost.
Docker Compose brings everything up together, connected in one network.
- Run:
docker-compose up -d --build
# test
curl http://localhost:5000/
Explanation: Compose creates a network so web can reach db at hostname db. volumes: persist DB data across container restarts.
Rebuild & restart [Optional]
From inside your flask-postgres project:
docker-compose down -v
docker-compose up -d --build
Test again:
curl http://localhost:5000/
Expected output:
flask -> postgres ok
Quick diagnostics (if still not working)
1. Check containers are running:
docker-compose ps
You should see:
web Up 0.0.0.0:5000->5000/tcp
db Up 5432/tcp
2. Check Flask logs:
docker logs flask-postgres-web-1
Look for:
Running on http://0.0.0.0:5000
3. Check port binding:
docker inspect flask-postgres-web-1 --format='{{json .NetworkSettings.Ports}}' | jq
Expect:
{"5000/tcp":[{"HostIp":"0.0.0.0","HostPort":"5000"}]}
A full demo todo app with CI patterns is available in Docker’s official getting-started todo repo. GitHub
Practical Docker concepts
- Image vs Container: image = filesystem + metadata; container = running instance.
- Layers & cache: Docker caches image layers — ordering instructions in Dockerfile affects rebuild speed. (Put
COPY package*.json+RUN npm cibefore copying rest.) - .dockerignore: exclude files from image build (node_modules, .git).
- Multi-stage build: compile/build in one stage, copy artifacts into smaller runtime stage to reduce image size.
- Volumes vs Bind mounts: volumes => managed by Docker (good for DB persistence). Bind mounts => map host dir (good for development).
- Docker network: services in Compose automatically join a network and can address each other by service name.
Cleanup commands
# stop & remove containers
docker rm -f $(docker ps -aq) || true
# remove unused images
docker image prune -a
# remove volumes
docker volume prune
Be careful — prune commands delete data!
Suggested reading & ready-made repos (run these to practice)
- Docker official “Get started” + hands-on tutorial (step-by-step). Docker Documentation
- Docker 101 self-paced hands-on tutorial. Docker
docker/getting-startedandgetting-started-appdemo repos (good for following a single guided tutorial including Dockerfile, Compose, CI). GitHub+1docker/awesome-compose— many Compose examples (WordPress, databases, multi-service apps). GitHubveggiemonk/awesome-docker— curated community list, good for exploring more projects and advanced topics. GitHub
