Docker Hands-on-demo applications

Mini-Project 1 — “Hello static site”

Goal: create a tiny static site, containerise it with nginx, run it, push image to Docker Hub.

  1. Create project dir:
mkdir hello-static && cd hello-static
  1. index.html:
<!-- index.html -->
<!doctype html>
<html><body><h1>Hello from Docker — static site</h1></body></html>
  1. Dockerfile (minimal, production style):
# Dockerfile
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
EXPOSE 80
  1. 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
  1. 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.

  1. Project layout:
flask-postgres/
  app/
    app.py
    requirements.txt
  docker-compose.yml
  Dockerfile
  1. 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)
  1. app/requirements.txt:
Flask
psycopg2-binary
  1. 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"]
  1. 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.

  1. 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 ci before 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)

  1. Docker official “Get started” + hands-on tutorial (step-by-step). Docker Documentation
  2. Docker 101 self-paced hands-on tutorial. Docker
  3. docker/getting-started and getting-started-app demo repos (good for following a single guided tutorial including Dockerfile, Compose, CI). GitHub+1
  4. docker/awesome-compose — many Compose examples (WordPress, databases, multi-service apps). GitHub
  5. veggiemonk/awesome-docker — curated community list, good for exploring more projects and advanced topics. GitHub
Scroll to Top