From 1ac9851e4812ce4ab9c26e349388631f4cfaea83 Mon Sep 17 00:00:00 2001 From: yberion Date: Mon, 18 May 2026 18:50:58 +0200 Subject: [PATCH] Initial commit: Schulungsunterlagen Tag 2 + Setup-Script Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 1 + README.md | 49 +++++++++ listings/visitcounter/app/Containerfile | 18 ++++ listings/visitcounter/app/go.mod | 5 + listings/visitcounter/app/main.go | 71 ++++++++++++ listings/visitcounter/docker-compose.yml | 48 +++++++++ listings/visitcounter/nginx/default.conf | 9 ++ setup/setup.sh | 131 +++++++++++++++++++++++ 8 files changed, 332 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 listings/visitcounter/app/Containerfile create mode 100644 listings/visitcounter/app/go.mod create mode 100644 listings/visitcounter/app/main.go create mode 100644 listings/visitcounter/docker-compose.yml create mode 100644 listings/visitcounter/nginx/default.conf create mode 100755 setup/setup.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f47e01f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tag3/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..0fdd9cc --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# Container Training — Participant Materials + +Welcome! This repository contains the code examples and listings for the 3-day container training. + +## How to get this repo + +```bash +git clone https://git.freemind.de/yberion/container-schulung.git +cd participants +``` + +## Structure + +``` +listings/ + visitcounter/ + app/ ← Go application (Tag 2) + main.go + go.mod + Containerfile + docker-compose.yml + nginx/ + default.conf +``` + +## Day 2 — Building and Running the App + +```bash +cd listings/visitcounter + +# Build the image +podman build -t visitcounter:latest app/ + +# Start the full stack +podman compose up -d + +# Check it's running +curl localhost:8080 +``` + +## Prerequisites + +- Podman installed (`podman --version`) +- podman-compose installed (`podman compose version`) +- Go is **not** required — it compiles inside the container + +## Questions? + +Ask your trainer. diff --git a/listings/visitcounter/app/Containerfile b/listings/visitcounter/app/Containerfile new file mode 100644 index 0000000..ee17a9e --- /dev/null +++ b/listings/visitcounter/app/Containerfile @@ -0,0 +1,18 @@ +# ── Stage 1: Kompilieren ───────────────────────────────────────────────────── +FROM golang:1.24-alpine AS builder +WORKDIR /build + +COPY main.go go.mod ./ +# go mod tidy lädt Abhängigkeiten und erzeugt go.sum im Build-Layer +# CGO_ENABLED=0 → statisch gelinktes Binary, keine libc-Abhängigkeit +RUN go mod tidy \ + && CGO_ENABLED=0 GOOS=linux \ + go build -ldflags="-s -w" -o visitcounter . + +# ── Stage 2: Minimales Laufzeit-Image ──────────────────────────────────────── +FROM scratch +# scratch = leeres Image ohne OS – das Binary läuft direkt auf dem Kernel +COPY --from=builder /build/visitcounter /visitcounter +EXPOSE 8080 +USER 65534 +ENTRYPOINT ["/visitcounter"] diff --git a/listings/visitcounter/app/go.mod b/listings/visitcounter/app/go.mod new file mode 100644 index 0000000..912775b --- /dev/null +++ b/listings/visitcounter/app/go.mod @@ -0,0 +1,5 @@ +module visitcounter + +go 1.24 + +require github.com/lib/pq v1.10.9 diff --git a/listings/visitcounter/app/main.go b/listings/visitcounter/app/main.go new file mode 100644 index 0000000..3a120a1 --- /dev/null +++ b/listings/visitcounter/app/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "net/http" + "os" + + _ "github.com/lib/pq" +) + +var db *sql.DB + +func getenv(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + return fallback +} + +func initDB() { + dsn := fmt.Sprintf( + "host=%s dbname=%s user=%s password=%s sslmode=disable", + getenv("DB_HOST", "db"), + getenv("DB_NAME", "visitcount"), + getenv("DB_USER", "app"), + getenv("DB_PASSWORD", "secret"), + ) + var err error + db, err = sql.Open("postgres", dsn) + if err != nil { + log.Fatal(err) + } + if err = db.Ping(); err != nil { + log.Fatal(err) + } + _, err = db.Exec( + `CREATE TABLE IF NOT EXISTS visits (count INTEGER DEFAULT 0);` + + `INSERT INTO visits SELECT 0 WHERE NOT EXISTS (SELECT 1 FROM visits);`, + ) + if err != nil { + log.Fatal(err) + } +} + +func handleIndex(w http.ResponseWriter, r *http.Request) { + var count int + err := db.QueryRow("UPDATE visits SET count = count + 1 RETURNING count").Scan(&count) + if err != nil { + http.Error(w, "database error", http.StatusInternalServerError) + return + } + fmt.Fprintf(w, "

VisitCounter

Visited %d times.

", count) +} + +func handleHealth(w http.ResponseWriter, r *http.Request) { + if err := db.Ping(); err != nil { + http.Error(w, "db unreachable", http.StatusServiceUnavailable) + return + } + fmt.Fprintln(w, "ok") +} + +func main() { + initDB() + http.HandleFunc("/", handleIndex) + http.HandleFunc("/health", handleHealth) + log.Println("listening on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} diff --git a/listings/visitcounter/docker-compose.yml b/listings/visitcounter/docker-compose.yml new file mode 100644 index 0000000..0ae058d --- /dev/null +++ b/listings/visitcounter/docker-compose.yml @@ -0,0 +1,48 @@ +services: + db: + image: postgres:16-alpine + environment: + POSTGRES_DB: visitcount + POSTGRES_USER: app + POSTGRES_PASSWORD: secret + volumes: + - db-data:/var/lib/postgresql/data + networks: + - app-net + healthcheck: + test: ["CMD-SHELL", "pg_isready -U app -d visitcount"] + interval: 5s + timeout: 3s + retries: 5 + + app: + build: + context: ./app + dockerfile: Containerfile + environment: + DB_HOST: db + DB_NAME: visitcount + DB_USER: app + DB_PASSWORD: secret + networks: + - app-net + depends_on: + db: + condition: service_healthy + + proxy: + image: nginx:1.27-alpine + ports: + - "8080:80" + volumes: + - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro + networks: + - app-net + depends_on: + - app + +volumes: + db-data: + +networks: + app-net: diff --git a/listings/visitcounter/nginx/default.conf b/listings/visitcounter/nginx/default.conf new file mode 100644 index 0000000..5e8edf8 --- /dev/null +++ b/listings/visitcounter/nginx/default.conf @@ -0,0 +1,9 @@ +server { + listen 80; + + location / { + proxy_pass http://app:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} diff --git a/setup/setup.sh b/setup/setup.sh new file mode 100755 index 0000000..d63341a --- /dev/null +++ b/setup/setup.sh @@ -0,0 +1,131 @@ +#!/bin/bash +# Container Training — Debian 12 Setup +# Als root oder mit sudo ausführen: sudo bash setup.sh + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +ok() { echo -e "${GREEN}[OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +fail() { echo -e "${RED}[FEHLER]${NC} $*"; exit 1; } + +[ "$(id -u)" -eq 0 ] || fail "Bitte als root oder mit sudo ausführen." + +echo "================================================" +echo " Container-Schulung — Setup (Debian 12)" +echo "================================================" +echo "" + +# Trainingsuser ermitteln +TRAINING_USER=${SUDO_USER:-""} +if [ -z "$TRAINING_USER" ]; then + warn "SUDO_USER nicht gesetzt — Linger wird nicht konfiguriert." + warn "Ggf. manuell ausführen: loginctl enable-linger " +fi + +# ------------------------------------------------------- +echo "" +echo "[1/5] Basis-Tools..." +# ------------------------------------------------------- +apt-get update -q +apt-get install -y \ + iputils-ping \ + iproute2 \ + net-tools \ + dnsutils \ + curl \ + wget \ + git \ + vim \ + jq \ + bash-completion +ok "Netzwerk-Tools installiert." + +# ------------------------------------------------------- +echo "" +echo "[2/5] Podman-Abhängigkeiten (rootless)..." +# ------------------------------------------------------- +apt-get install -y \ + slirp4netns \ + fuse-overlayfs \ + uidmap \ + dbus-user-session \ + systemd-container +ok "Rootless-Abhängigkeiten installiert." + +# ------------------------------------------------------- +echo "" +echo "[3/5] Podman (Kubic-Repo — brauchen >= 4.4 für Quadlets)..." +# ------------------------------------------------------- +echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Debian_12/ /' \ + | tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list +curl -fsSL https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/Debian_12/Release.key \ + | gpg --dearmor \ + | tee /etc/apt/trusted.gpg.d/devel_kubic_libcontainers_stable.gpg > /dev/null +apt-get update -q +apt-get install -y podman + +PODMAN_VER=$(podman --version | awk '{print $3}') +PODMAN_MINOR=$(echo "$PODMAN_VER" | cut -d. -f2) +PODMAN_MAJOR=$(echo "$PODMAN_VER" | cut -d. -f1) + +if [ "$PODMAN_MAJOR" -gt 4 ] || { [ "$PODMAN_MAJOR" -eq 4 ] && [ "$PODMAN_MINOR" -ge 4 ]; }; then + ok "Podman $PODMAN_VER — Quadlets werden unterstützt." +else + warn "Podman $PODMAN_VER — Quadlets brauchen >= 4.4! Tag 3 könnte Probleme machen." +fi + +# ------------------------------------------------------- +echo "" +echo "[4/5] podman-compose + trivy..." +# ------------------------------------------------------- +apt-get install -y podman-compose 2>/dev/null \ + || pip3 install --quiet podman-compose +ok "podman-compose installiert." + +# trivy +curl -fsSL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \ + | sh -s -- -b /usr/local/bin latest +ok "trivy installiert: $(trivy --version | head -1)" + +# ------------------------------------------------------- +echo "" +echo "[5/5] Benutzer-Setup ($TRAINING_USER)..." +# ------------------------------------------------------- +if [ -n "$TRAINING_USER" ]; then + loginctl enable-linger "$TRAINING_USER" + ok "Linger aktiviert für $TRAINING_USER — systemctl --user überlebt Logout." + + # subuid/subgid für rootless Container + if ! grep -q "^${TRAINING_USER}:" /etc/subuid 2>/dev/null; then + usermod --add-subuids 100000-165535 "$TRAINING_USER" + usermod --add-subgids 100000-165535 "$TRAINING_USER" + ok "subuid/subgid konfiguriert." + else + ok "subuid/subgid bereits vorhanden." + fi +else + warn "Kein Trainingsuser — Linger und subuid/subgid übersprungen." +fi + +# ------------------------------------------------------- +echo "" +echo "================================================" +echo " Zusammenfassung" +echo "================================================" +podman --version +podman-compose --version 2>/dev/null | head -1 || true +trivy --version | head -1 +echo "" + +if [ -n "$TRAINING_USER" ]; then + echo "Benutzer: $TRAINING_USER" + echo "Linger: $(loginctl show-user "$TRAINING_USER" --property=Linger --value 2>/dev/null || echo 'unbekannt')" +fi + +echo "" +ok "Setup abgeschlossen. Bitte einmal aus- und wieder einloggen."