Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions app_go/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]

65 changes: 65 additions & 0 deletions app_go/docs/LAB02.md
Original file line number Diff line number Diff line change
@@ -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: `<size>` (approx)
- Multi-stage: `<size>` (approx)
- **Reduction:** `<difference>` 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`.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions app_python/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
__pycache__/
*.py[cod]
venv/
.venv/
.git/
.gitignore
docs/
tests/
*.md
.vscode/
.idea/
.DS_Store
*.log
24 changes: 24 additions & 0 deletions app_python/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
7 changes: 7 additions & 0 deletions app_python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <image-name> .`

**Run:** `docker run -p <host-port>:5000 <image-name>` (app listens on 5000 inside container)

**Pull from Docker Hub:** `docker pull <your-dockerhub-username>/<repo-name>:<tag>` then run as above.
73 changes: 73 additions & 0 deletions app_python/docs/LAB02.md
Original file line number Diff line number Diff line change
@@ -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 <your-image>` 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/`)

```
<paste your `docker build -t <name> .` output here>
```

**Run:**

```
<paste your `docker run -p 5000:5000 <name>` 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!
Binary file added app_python/docs/screenshots/lab2-check-health.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.