Initial commit: Schulungsunterlagen Tag 2 + Setup-Script

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
yberion 2026-05-18 18:50:58 +02:00
commit 1ac9851e48
8 changed files with 332 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
tag3/

49
README.md Normal file
View file

@ -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.

View file

@ -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"]

View file

@ -0,0 +1,5 @@
module visitcounter
go 1.24
require github.com/lib/pq v1.10.9

View file

@ -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, "<h1>VisitCounter</h1><p>Visited <strong>%d</strong> times.</p>", 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))
}

View file

@ -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:

View file

@ -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;
}
}

131
setup/setup.sh Executable file
View file

@ -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 <username>"
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."