diff --git a/app_go/Dockerfile b/app_go/Dockerfile new file mode 100644 index 0000000000..0b2ceb5085 --- /dev/null +++ b/app_go/Dockerfile @@ -0,0 +1,35 @@ +## Stage 1: Builder +FROM golang:1.22-alpine AS builder + +WORKDIR /app + +# Cache modules first +COPY go.mod ./ +RUN go mod download + +# Copy source +COPY . . + +# Build statically-linked binary (simple CGO-disabled build) +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o devops-info-service-go . + +## Stage 2: Runtime +FROM alpine:3.20 + +WORKDIR /app + +# Non-root user for security +RUN addgroup -S appgroup && adduser -S appuser -G appgroup + +# Copy only the compiled binary from builder image +COPY --from=builder /app/devops-info-service-go /app/devops-info-service-go + +USER appuser + +EXPOSE 8080 + +ENV HOST=0.0.0.0 +ENV PORT=8080 + +ENTRYPOINT ["/app/devops-info-service-go"] + diff --git a/app_go/docs/LAB02.md b/app_go/docs/LAB02.md new file mode 100644 index 0000000000..56ba1de702 --- /dev/null +++ b/app_go/docs/LAB02.md @@ -0,0 +1,65 @@ +# Lab 2 Bonus — Go Multi-Stage Build + +## 1. Multi-Stage Strategy + +- **Builder stage (`golang:1.22-alpine`)**: has the Go toolchain and modules; compiles the app. +- **Runtime stage (`alpine:3.20`)**: minimal image with only the compiled binary and a non-root user. +- `CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build` produces a binary that runs fine on the tiny Alpine base. + +Key idea: keep compilers and build tools only in the first stage; ship just the binary in the final image. + +## 2. Size Comparison + +Run these locally and record the numbers: + +```bash +cd app_go + +# Builder-style image (if you build a single-stage image for comparison) +docker build -t devops-info-go:single -f Dockerfile.single . +docker images devops-info-go:single + +# Multi-stage image (this Dockerfile) +docker build -t devops-info-go:multi . +docker images devops-info-go:multi +``` + +**Your results (example format):** + +- Single-stage: `` (approx) +- Multi-stage: `` (approx) +- **Reduction:** `` saved → smaller attack surface, faster pulls. + +## 3. Why Multi-Stage Matters + +- **Smaller images:** no Go toolchain, headers, or build cache in the final image. +- **Security:** fewer binaries and packages → fewer vulnerabilities and a tighter attack surface. +- **Clean separation of concerns:** build environment vs. runtime environment. + +## 4. Build & Run + +```bash +cd app_go +docker build -t devops-info-go:multi . +docker run -p 8080:8080 devops-info-go:multi +``` + +Test endpoints: + +```bash +curl http://localhost:8080/ +curl http://localhost:8080/health +``` + +![healthcheck](./screenshots/multistage-healthcheck.png) + +## 5. Stage-by-Stage Explanation + +- **Stage 1 (builder):** + - Uses `golang:1.22-alpine`. + - Downloads modules (`go mod download`) and builds the binary. +- **Stage 2 (runtime):** + - Uses `alpine:3.20` with a non-root user (`appuser`). + - Copies only `/app/devops-info-service-go` from the builder. + - Exposes port `8080` and starts the binary as `ENTRYPOINT`. + diff --git a/app_go/docs/screenshots/multistage-healthcheck.png b/app_go/docs/screenshots/multistage-healthcheck.png new file mode 100644 index 0000000000..57d6530f74 Binary files /dev/null and b/app_go/docs/screenshots/multistage-healthcheck.png differ diff --git a/app_python/.dockerignore b/app_python/.dockerignore new file mode 100644 index 0000000000..5548bef098 --- /dev/null +++ b/app_python/.dockerignore @@ -0,0 +1,13 @@ +__pycache__/ +*.py[cod] +venv/ +.venv/ +.git/ +.gitignore +docs/ +tests/ +*.md +.vscode/ +.idea/ +.DS_Store +*.log diff --git a/app_python/Dockerfile b/app_python/Dockerfile new file mode 100644 index 0000000000..70302a0da3 --- /dev/null +++ b/app_python/Dockerfile @@ -0,0 +1,24 @@ +# Use specific version for reproducibility +FROM python:3.13-slim + +# Create non-root user (security: don't run as root) +RUN groupadd --gid 1000 appgroup \ + && useradd --uid 1000 --gid appgroup --shell /bin/bash --create-home appuser + +WORKDIR /app + +# Dependencies first (better layer caching: code changes don't invalidate this) +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Application code +COPY app.py . + +# Own files so non-root user can read +RUN chown -R appuser:appgroup /app + +USER appuser + +EXPOSE 5000 + +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "5000"] diff --git a/app_python/README.md b/app_python/README.md index 1a1cafc112..9cb270f8b8 100644 --- a/app_python/README.md +++ b/app_python/README.md @@ -72,3 +72,10 @@ Example: HOST=127.0.0.1 PORT=3000 DEBUG=true python app_python/app.py ``` +## Docker + +**Build:** From `app_python/`, run `docker build -t .` + +**Run:** `docker run -p :5000 ` (app listens on 5000 inside container) + +**Pull from Docker Hub:** `docker pull /:` then run as above. diff --git a/app_python/docs/LAB02.md b/app_python/docs/LAB02.md new file mode 100644 index 0000000000..80af642335 --- /dev/null +++ b/app_python/docs/LAB02.md @@ -0,0 +1,73 @@ +# Lab 2 — Docker Containerization + +## 1. Docker Best Practices Applied + +| Practice | Why it matters | +|----------|----------------| +| **Non-root user** | Reduces blast radius if the app or image is compromised; root inside container can be abused. | +| **Specific base version** (`python:3.13-slim`) | Reproducible builds; avoids surprise breakage when base image updates. | +| **Layer order** | Copy `requirements.txt` and run `pip install` before copying app code. Code changes then only invalidate the last layer; dependency layer is cached. | +| **Only copy necessary files** | Smaller build context and image; fewer secrets/artifacts in the image. | +| **`.dockerignore`** | Excludes dev/test/docs from build context → faster builds and no accidental inclusion of unneeded files. | +| **`EXPOSE 5000`** | Documents the port the app uses; doesn’t publish it (that’s `docker run -p`). | + +**Snippet (layer order + non-root):** + +```dockerfile +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY app.py . +# ... +USER appuser +``` + +## 2. Image Information & Decisions + +- **Base:** `python:3.13-slim` — matches lab stack, smaller than full Python image, still has common libs (unlike alpine, which can cause build issues with some wheels). +- **Size:** Check with `docker images ` after build. Slim base keeps it moderate; no extra tools in the final layer. +- **Layers:** Base → user creation → WORKDIR → requirements copy + pip → app copy → chown → USER → EXPOSE/CMD. Dependency layer is reused when only code changes. + +## 3. Build & Run Process + +**Build:** (run from `app_python/`) + +``` + .` output here> +``` + +**Run:** + +``` +` output here> +``` + +**Test endpoints:** + +``` +curl http://localhost:5000/ +{"service":{"name":"devops-info-service","version":"1.0.0","description":"DevOps course info service","framework":"FastAPI"},"system":{"hostname":"89f830ea2369","platform":"Linux","platform_version":"Linux-6.17.0-12-generic-x86_64-with-glibc2.41","architecture":"x86_64","cpu_count":12,"python_version":"3.13.11"},"runtime":{"uptime_seconds":3,"uptime_human":"0 hours, 0 minutes","current_time":"2026-02-04T09:18:05.987481+00:00","timezone":"UTC"},"request":{"client_ip":"172.17.0.1","user_agent":"curl/8.14.1","method":"GET","path":"/"},"endpoints":[{"path":"/","method":"GET","description":"Service information"},{"path":"/health","method":"GET","description":"Health check"}]} + +curl http://localhost:5000/health +{"status":"healthy","timestamp":"2026-02-04T09:18:08.395779+00:00","uptime_seconds":6} +``` + +**Docker Hub:** +Repository URL: `https://hub.docker.com/r/woolfer0097kek/devops-course-lab2` + +## 4. Technical Analysis + +- **Why it works:** Uvicorn runs as `appuser`, binds to `0.0.0.0:5000` so the host can reach it when you use `-p`. Dependencies are installed in an earlier layer, so the app has FastAPI/uvicorn available. +- **Layer order:** If we copied everything first and then ran `pip install`, any change to `app.py` would invalidate the cache and re-run `pip install` every time. Putting dependencies first keeps installs cached. +- **Security:** Non-root user, minimal files in the image, no dev/test tooling. `.dockerignore` keeps `.git` and secrets out of the build context. +- **`.dockerignore`:** Reduces context sent to the daemon (faster `docker build`) and prevents `docs/`, `tests/`, `venv/`, `.git` from being considered for `COPY`, so they never end up in the image. + +## 5. Challenges & Solutions + +- I didn't encounter any serious challenges, because its quite routine task for me. However, i didn't really care about multi-stage building before this lab, but after comparisson I was quite impressed and will think about it in my work in future. + +## 6. Multi-stage VS single +woolfer0097kek/devops-course-image-lab2-go 2.0 398779964a25 3 seconds ago 15MB +woolfer0097kek/devops-course-image-lab2 latest 9b3e60f18070 16 minutes ago 164MB + +i wrote go dockerfile with multistage building strategy while python uses single-stage building +There is enourmous difference in its sizes! 15MB VS 164MB! \ No newline at end of file diff --git a/app_python/docs/screenshots/lab2-check-health.png b/app_python/docs/screenshots/lab2-check-health.png new file mode 100644 index 0000000000..a25db108d1 Binary files /dev/null and b/app_python/docs/screenshots/lab2-check-health.png differ