diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 000000000..1a68db5e5
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,13 @@
+## Goal
+
+
+## Changes
+-
+
+## Testing
+
+
+## Checklist
+- [ ] Title is a clear sentence (≤ 70 chars)
+- [ ] Commits are signed (`git log --show-signature`)
+- [ ] `submissions/labN.md` updated
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 000000000..44f031e1c
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,67 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ paths:
+ - 'app/**'
+ - '.github/workflows/**'
+
+permissions:
+ contents: read
+
+jobs:
+ vet:
+ runs-on: ubuntu-24.04
+ strategy:
+ fail-fast: false
+ matrix:
+ go: ['1.23', '1.24']
+ steps:
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.2.2
+ - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
+ with:
+ go-version: ${{ matrix.go }}
+ cache-dependency-path: app/go.mod
+ - name: go vet
+ working-directory: app
+ run: go vet ./...
+
+ test:
+ runs-on: ubuntu-24.04
+ strategy:
+ fail-fast: false
+ matrix:
+ go: ['1.23', '1.24']
+ steps:
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.2.2
+ - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
+ with:
+ go-version: ${{ matrix.go }}
+ cache-dependency-path: app/go.mod
+ - name: go test -race
+ working-directory: app
+ run: go test -race -count=1 ./...
+
+ lint:
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.2.2
+ - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
+ with:
+ go-version: '1.24'
+ cache-dependency-path: app/go.mod
+ - uses: golangci/golangci-lint-action@9fae48acfc02a90574d7c304a1758ef9895495fa # v7.0.1
+ with:
+ version: v2.5.0
+ working-directory: app
+
+ ci-ok:
+ if: always()
+ needs: [vet, test, lint]
+ runs-on: ubuntu-24.04
+ steps:
+ - run: |
+ test "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" = "false"
\ No newline at end of file
diff --git a/submissions/failed.png b/submissions/failed.png
new file mode 100644
index 000000000..463f2a25e
Binary files /dev/null and b/submissions/failed.png differ
diff --git a/submissions/lab1.md b/submissions/lab1.md
new file mode 100644
index 000000000..33da79068
--- /dev/null
+++ b/submissions/lab1.md
@@ -0,0 +1,93 @@
+# Lab 1 submission
+
+## Task 1
+Request:
+```
+curl -s http://localhost:8080/health | python3 -m json.tool
+```
+
+Answer:
+```
+{
+ "notes": 5,
+ "status": "ok"
+}
+```
+
+Request:
+```
+curl -s http://localhost:8080/notes | python3 -m json.tool
+```
+
+Answer:
+```
+[
+ {
+ "id": 2,
+ "title": "Read app/main.go first",
+ "body": "Start by understanding the entry point \u2014 env vars, signal handling, graceful shutdown.",
+ "created_at": "2026-01-15T10:05:00Z"
+ },
+ {
+ "id": 3,
+ "title": "DevOps mantra",
+ "body": "If it hurts, do it more often.",
+ "created_at": "2026-01-15T10:10:00Z"
+ },
+ {
+ "id": 4,
+ "title": "Endpoint cheat-sheet",
+ "body": "GET /notes GET /notes/{id} POST /notes DELETE /notes/{id} GET /health GET /metrics",
+ "created_at": "2026-01-15T10:15:00Z"
+ },
+ {
+ "id": 1,
+ "title": "Welcome to QuickNotes",
+ "body": "This is the project you'll containerize, deploy, monitor, and harden across all 10 labs.",
+ "created_at": "2026-01-15T10:00:00Z"
+ }
+]
+```
+
+Request:
+```
+curl -s -X POST http://localhost:8080/notes \
+ -H 'Content-Type: application/json' \
+ -d '{"title":"hello","body":"first POST"}' | python3 -m json.tool
+```
+
+Answer:
+```
+{
+ "id": 5,
+ "title": "hello",
+ "body": "first POST",
+ "created_at": "2026-06-05T10:51:13.503497Z"
+},
+```
+
+```
+git log --show-signature -1
+
+commit 843a27f3ade36ea41d723f168fb3f8c9c1f7b70c (HEAD -> feature/lab1, origin/feature/lab1)
+Good "git" signature for 15dnau@gmail.com with ED25519 key SHA256:k0n7/mx/uRX52s/zu9pxaN+h/IKnBJzcnuybJgthVkM
+Author: Dmitrii <15dnau@gmail.com>
+Date: Fri Jun 5 14:03:50 2026 +0300
+
+ docs(lab1): start submission
+
+ Signed-off-by: Dmitrii <15dnau@gmail.com>
+```
+
+### Verified commit
+
+
+
+When we work with Github we trust that commit made by Dmitrii was actually made by Dmitrii. However Git itself does not verify commit's author. Anyone can set any name and make a commit, therefore we want commits to be verified.
+
+### GitHub Community
+Why starring repositories matters in open source
+For a project, stars are a signal of trust and relevance. Moreover, starring is something like bookmarking a repository.
+
+How following developers helps in team projects and professional growth
+Following your colleagues on GitHub gives you a low-noise feed of their activity
\ No newline at end of file
diff --git a/submissions/lab3.md b/submissions/lab3.md
new file mode 100644
index 000000000..cfb3068e0
--- /dev/null
+++ b/submissions/lab3.md
@@ -0,0 +1,148 @@
+GitHub. I have some troubles with signing into Innopolis GitLab account.
+
+### Green CI run
+https://github.com/Dnau15/DevOps-Intro/actions/runs/27645595628
+
+
+All three units passed: `vet`, `test`, `lint`.
+
+### Failed run + fix (Task 1.5)
+To prove the gate blocks a broken PR, I changed an expected value in
+`app/handlers_test.go`, pushed, and the `test` check went **red** and the
+PR became un-mergeable.
+
+- ❌ Failed run: ``
+- 🔧 Fix commit (reverted the breakage, check green again): ``
+
+
+
+### Branch protection (Task 1.6)
+`main` on my fork requires the status checks to pass before merging, and
+requires branches to be up to date.
+
+
+
+---
+
+## Design Questions (1.2)
+
+### a) Why pin `ubuntu-24.04` instead of `ubuntu-latest`?
+`ubuntu-latest` is a **moving alias**: GitHub periodically re-points it to a
+newer LTS (e.g. 22.04 → 24.04). When that flip happens, the pre-installed
+toolchain, system libraries (glibc), and default packages change **under
+you** — a PR that was green yesterday can fail today with zero code changes,
+and the failure is hard to diagnose because nothing in your repo moved.
+Pinning `ubuntu-24.04` makes the environment **reproducible**: the same
+commit builds in the same environment months later. What breaks otherwise:
+silent toolchain/library bumps, removed CLI tools your steps assumed were
+present, and non-deterministic, "spooky-action-at-a-distance" CI failures.
+
+### b) Why split vet + test + lint into separate units?
+Two reasons: **parallelism** and **isolation**.
+- *Parallelism:* three independent jobs run concurrently on separate
+ runners, so wall-clock ≈ the slowest single job instead of the sum.
+- *Isolation:* each unit gets its own status check, so a red ✗ tells you
+ *immediately* whether it's a vet, test, or lint problem.
+
+In one combined job they run **serially in a single shell**, and the shell
+stops at the first failing command — so a `go vet` failure aborts before
+tests ever run, hiding whether the tests pass. You also can't require or
+re-run them independently in branch protection. Splitting gives faster,
+clearer, granular feedback.
+
+### c) What attack does SHA pinning prevent? (incident name + date)
+The **tj-actions/changed-files supply-chain compromise, March 2025**
+(~March 14, 2025). Mutable references like `@v44` or `@v1` are **tags**, and
+a tag can be silently re-pointed to a different commit. In the tj-actions
+incident the attacker repointed the action's tags to a malicious commit that
+dumped CI runner memory — leaking secrets into the build logs of *every*
+repo that referenced the action by tag. Pinning to a **full 40-char commit
+SHA** makes the reference **immutable**: even if the tag is moved, your
+workflow keeps running the exact reviewed commit, so a hijacked tag can't
+inject new code into your pipeline.
+
+### d) What is `permissions:` and the principle behind it?
+`permissions:` declares the scopes granted to the automatic `GITHUB_TOKEN`
+for the workflow/job (e.g. `contents`, `pull-requests`, `packages` —
+each `read`/`write`/`none`). The principle is **least privilege**: grant only
+what the job actually needs. A build-and-test pipeline only needs to read the
+code, so `contents: read` is enough. If a step or a compromised third-party
+action turns malicious, a read-only token sharply limits the blast radius —
+it can't push commits, cut releases, or alter issues/PRs. Starting from
+`contents: read` and adding scopes narrowly (only when a step needs them) is
+the safe default.
+
+### e) (GitLab) stage vs job; what `dependencies:` adds over `stages:`
+*(Answered for completeness even though I took the GitHub path.)*
+A **job** is a single unit of work — one `script` executed by a runner.
+A **stage** is a named group of jobs: all jobs in a stage run **in parallel**,
+and stages themselves run **sequentially** — every job in stage N must
+succeed before stage N+1 starts. So `stages:` controls **ordering**.
+`dependencies:` is about **artifact flow**: it specifies which earlier jobs'
+artifacts a job downloads, independent of ordering. With `dependencies: []`
+a job pulls *no* artifacts (faster, cleaner), and combined with `needs:` you
+can build a DAG where a job starts as soon as its specific dependencies
+finish — instead of waiting for the whole previous stage. In short:
+`stages:` = execution order; `dependencies:` = which artifacts get passed.
+
+## Task 2 — Make It Fast and Smart
+
+### Optimizations applied
+- **Dependency cache** — enabled `actions/setup-go` caching
+ (`cache-dependency-path: app/go.mod`). It restores the Go module + build
+ cache between runs. Visible in the log as the "Restore cache" / "Save cache"
+ steps.
+- **Build matrix** — `vet` and `test` now run against Go **1.23** and **1.24**
+ in parallel via `strategy.matrix` with `fail-fast: false`, so a failure on
+ one toolchain still reports the result for the other.
+- **Path filter** — `on.pull_request.paths` restricts runs to changes under
+ `app/**` or `.github/workflows/**`; docs-only PRs (e.g. README) skip CI
+ entirely.
+- **`ci-ok` aggregation job** — a single required check (`if: always()`,
+ `needs: [vet, test, lint]`) so the matrix can be changed freely without
+ re-editing branch protection.
+
+### Timing (median of 5 runs)
+| Scenario | Wall-clock |
+|----------|-----------|
+| Baseline (no cache, single Go 1.24, no path filter) | **39 s** |
+| With cache | **38 s** |
+| With cache + matrix | **52 s** |
+
+**zero third-party dependencies** — `app/go.mod` has no `require` block and
+there is no `go.sum` — so the module cache has nothing to store. The dominant
+costs are runner provisioning, `actions/checkout`, and the Go toolchain
+download, none of which `setup-go`'s module cache touches. The matrix row is
+*higher* (52 s) because it runs four `vet`/`test` cells plus `lint`; even
+though the cells run in parallel, each pays its own provisioning + toolchain
+download, and wall-clock is bounded by the slowest cell plus the serial `lint`
+job. On a dependency-heavy project the cache row would drop sharply; here it is
+correctly boring.
+
+### Design questions
+
+**f) Why cache `go.sum`-keyed inputs and not build outputs?**
+Inputs are deterministic and content-addressed: a given `go.sum` always maps to
+the exact same module bytes, so a cache keyed on `hash(go.sum)` is safe — a hit
+is guaranteed correct, and changing a dependency changes the key, which
+naturally invalidates the cache. Build *outputs* depend on the compiler
+version, build flags, and `GOOS`/`GOARCH`; subtle variation can yield stale or
+mismatched artifacts that are silently wrong and hard to validate. Caching
+inputs trades a cheap rebuild for a correctness guarantee; caching outputs
+risks poisoning correctness for marginal speed.
+
+**g) What does `fail-fast: false` change, and when do you want `true`?**
+With `fail-fast: false`, one failing matrix cell does **not** cancel the
+others — every combination runs to completion, so you can see *which* Go
+version broke (essential for diagnosing toolchain-specific bugs). The default
+`fail-fast: true` cancels all in-progress and pending cells the moment one
+fails. You want `true` when the matrix is large and expensive and you only need
+fast "something broke" feedback to block a merge and save CI minutes; `false`
+when per-cell diagnostics matter — which is our case.
+
+**h) Risk of an attacker writing a cache from a malicious PR that a protected
+branch later reads?**
+This is **cache poisoning**: a PR workflow can write a cache entry; if a run on
+a protected branch later restored it, the attacker's tampered artifacts would
+execute with the trust and permissions of `main`, enabling code execution or
+secret exfiltration.
diff --git a/submissions/protected_lab3.png b/submissions/protected_lab3.png
new file mode 100644
index 000000000..b87c90c64
Binary files /dev/null and b/submissions/protected_lab3.png differ
diff --git a/submissions/verified.png b/submissions/verified.png
new file mode 100644
index 000000000..e7f1e43dd
Binary files /dev/null and b/submissions/verified.png differ