Skip to content
Closed
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
13 changes: 13 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Goal
- What does this PR accomplish?

## Changes
- What was modified?

## Testing
- What was modified?

### Checklist:
- [ ] Clear title and description.
- [ ] Documentation/README updated if needed.
- [ ] No secrets or large temporary files.
44 changes: 44 additions & 0 deletions .github/workflows/github-actions-demo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀

on:
workflow_dispatch:

jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "Triggered by ${{ github.event_name }}"

- name: Check out repository code
uses: actions/checkout@v5

- name: System info
run: |
echo ""
echo "========================================"
echo "SYSTEM INFORMATION"
echo "========================================"

echo ""
echo "--- RUNNER INFORMATION ---"
echo "Runner name: ${{ runner.name }}"
echo "Runner OS: ${{ runner.os }}"
echo "Runner architecture: ${{ runner.arch }}"

echo ""
echo "--- OPERATING SYSTEM DETAILS ---"
cat /etc/os-release

echo ""
echo "--- HARDWARE SPECIFICATIONS ---"
echo "CPU Model: $(lscpu | grep 'Model name' | cut -d':' -f2 | xargs)"
echo "CPU Cores: $(nproc)"
echo "CPU Architecture: $(uname -m)"
echo "Total Memory: $(free -h | grep 'Mem:' | awk '{print $2}')"
echo "Available Memory: $(free -h | grep 'Mem:' | awk '{print $7}')"
echo "Disk Size: $(df -h / | tail -1 | awk '{print $2}')"
echo "Disk Usage: $(df -h / | tail -1 | awk '{print $5}')"

echo ""
echo "========================================"
4 changes: 4 additions & 0 deletions labs/lab11/app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM golang:1.22
WORKDIR /app
COPY main.go .
RUN go build -o app main.go
12 changes: 12 additions & 0 deletions labs/lab11/app/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{ pkgs ? import <nixpkgs> {} }:

pkgs.buildGoModule rec {
pname = "app";
version = "0.1.0";

src = ./.;

vendorHash = null;

doCheck = false;
}
19 changes: 19 additions & 0 deletions labs/lab11/app/docker.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{ pkgs ? import <nixpkgs> {} }:

let
app = pkgs.buildGoModule {
pname = "app";
version = "0.1.0";
src = ./.;
vendorHash = null;
doCheck = false;
};
in
pkgs.dockerTools.buildLayeredImage {
name = "nix-app";
tag = "latest";
contents = [ app ];
config = {
Entrypoint = [ "${app}/bin/app" ];
};
}
32 changes: 32 additions & 0 deletions labs/lab11/app/flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
description = "Reproducible Go app with Nix";

inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };

app = pkgs.buildGoModule {
pname = "app";
version = "0.1.0";
src = ./.;
vendorHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
doCheck = false;
};
in
{
packages.${system}.default = app;

dockerImages.${system}.default = pkgs.dockerTools.buildLayeredImage {
name = "nix-app";
contents = [ app ];
config.Entrypoint = [ "${app}/bin/app" ];
};

devShells.${system}.default = pkgs.mkShell {
buildInputs = [ pkgs.go pkgs.gopls ];
};
};
}
3 changes: 3 additions & 0 deletions labs/lab11/app/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module app

go 1.24.3
11 changes: 11 additions & 0 deletions labs/lab11/app/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
"fmt"
"time"
)

func main() {
fmt.Printf("Built with Nix at compile time\n")
fmt.Printf("Running at: %s\n", time.Now().Format(time.RFC3339))
}
Binary file added labs/screenshot/Img1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added labs/screenshot/Img2.png
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 labs/submission1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Lab 1 Submission
## Task 1 SSH Commit Signature Verification:
The signature of commits provides a cryptographic guarantee of authorship and code integrity, preventing forgery of commits and protecting against the introduction of malicious changes on someone else's behalf. In DevOps practices, this is critically important, since CI/CD systems trust only verified commits for automatic builds and deployments, which creates an additional level of security in the software supply chain. Signed commits also ensure compliance with security standards, provide an auditable trace of changes, and are displayed on platforms like GitHub with the "Verified" label, increasing trust in the code in team and open-source development.
#### Screenshots for Task 1:
![Verified Commit](screenshot/img1.png)

PR templates standardize the code review process, forcing developers to describe changes in a structured manner. All PRS receive the same sections ("Purpose", "Changes", "Testing"), which saves reviewers time — they do not need to search for information in chaotic descriptions. Checklists automatically remind you of important actions.: tests, documentation, code style. In distributed teams, templates compensate for the lack of personal communication — a reviewer in a different time zone receives all the necessary information without additional questions.
## Task 2 PR Template & Checklist:
PR templates standardize the code review process, forcing developers to describe changes in a structured manner. All PR receive the same sections ("Purpose", "Changes", "Testing"), which saves reviewers time — they do not need to search for information in chaotic descriptions. Checklists automatically remind you of important actions.: tests, documentation, code style. In distributed teams, templates compensate for the lack of personal communication — a reviewer in a different time zone receives all the necessary information without additional questions.

Challenges emerged during setup. I have some problem, while do Signing keys.
#### Screenshots for Task 2:
![PR template auto-filling the description.](screenshot/img2.png)
214 changes: 214 additions & 0 deletions labs/submission11.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
# Lab 11 — Reproducible Builds with Nix

**Platform:** WSL2 (Ubuntu 24.04.2 LTS) on Windows 11
**Student:** Lev Permiakov
**Date:** 2026-05-04

---

## Task 1 — Build Reproducible Artifacts from Scratch

### 1.1 Environment Setup

```
# Install Nix (Determinate Systems)
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
exec $SHELL
nix --version
# Output: nix (Determinate Nix 3.19.0) 2.34.6
```

### 1.2 Application Files

**main.go:**
```
package main
import (
"fmt"
"time"
)
func main() {
fmt.Printf("Built with Nix at compile time\n")
fmt.Printf("Running at: %s\n", time.Now().Format(time.RFC3339))
}
```

**go.mod:**
```
module app
go 1.22
```

**default.nix:**
```
{ pkgs ? import <nixpkgs> {} }:
pkgs.buildGoModule rec {
pname = "app";
version = "0.1.0";
src = ./.;
vendorHash = null; # no external dependencies
doCheck = false;
modVendor = false;
}
```

### 1.3 Reproducibility Verification

**First build:**
```
$ nix-build
/nix/store/r7kcjs6976b4bviwyww1qzr0h6jl5q1r-app-0.1.0

$ readlink result
/nix/store/r7kcjs6976b4bviwyww1qzr0h6jl5q1r-app-0.1.0

$ sha256sum ./result/bin/app
99efa7adac0d8fa51ce4088f1c1a9edf9e70f21c2f8bbe885ca25d16dcded35f ./result/bin/app
```

**Second build (after `rm result`):**
```
$ nix-build
/nix/store/r7kcjs6976b4bviwyww1qzr0h6jl5q1r-app-0.1.0 # identical path

$ sha256sum ./result/bin/app
99efa7adac0d8fa51ce4088f1c1a9edf9e70f21c2f8bbe885ca25d16dcded35f # identical hash
```

### 1.4 Docker Comparison (Non-Reproducible)

**Dockerfile:**
```
FROM golang:1.22
WORKDIR /app
COPY main.go .
RUN go build -o app main.go
```

**Build results:**
```
# First build: 25.9s, manifest sha256:fc25df0714bc7a2c902c4988f22dae3e1f34e2848f9875b64672c06a5e662bbf
# Second build: 1.0s (cached), attestation manifest sha256:d2051970a0e94700862b511a4fc7900d6edef1edd519fb0e74f81912a6469ec2
```

**Observation:** Despite layer caching, Docker generates different metadata hashes (attestation manifests, config) between builds due to timestamps and build context variations.

### 1.5 Analysis: Why Nix is Reproducible

| Factor | Nix | Traditional Docker |
|--------|-----|-------------------|
| **Dependency resolution** | Pinned via nixpkgs hash | `apt-get`/`go get` may fetch newer versions |
| **Build isolation** | Sandboxed, pure environment | Inherits host environment variables |
| **Output addressing** | Content-addressable store path | Layers with timestamps and metadata |
| **Hash determinism** | Same inputs → same path | Same Dockerfile ≠ same image hash |

**Nix Store Path Format:**
`/nix/store/<hash>-<name>-<version>`
- `<hash>`: SHA-256 of all build inputs (source, dependencies, build script, environment)
- Guarantees: identical inputs → identical outputs

---

## Task 2 — Reproducible Docker Images with Nix

### 2.1 Nix Docker Definition (`docker.nix`)

```
{ pkgs ? import <nixpkgs> {} }:
let
app = pkgs.buildGoModule {
pname = "app";
version = "0.1.0";
src = ./.;
vendorHash = null;
doCheck = false;
modVendor = false;
};
in
pkgs.dockerTools.buildLayeredImage {
name = "nix-app";
tag = "latest";
contents = [ app pkgs.cacert ];
config = {
Entrypoint = [ "${app}/bin/app" ];
Env = [ "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ];
};
created = "1970-01-01T00:00:01Z"; # fixed timestamp
}
```

### 2.2 Build and Execution

```
# Convert line endings (important for WSL/Windows)
$ dos2unix docker.nix default.nix

# Build image
$ nix-build docker.nix
/nix/store/s93ki1a13y5iba2n5fq5bi8iwlbvhwnx-nix-app.tar.gz

# Load into Docker (result is a file, not a directory!)
$ docker load < result
Loaded image: nix-app:latest

# Check size
$ docker images | grep nix-app
nix-app latest 2b4bb772d276 56 years ago 11.9MB

# Run container
$ docker run --rm nix-app:latest
Built with Nix at compile time
Running at: 2026-05-04T08:54:39Z
```

### 2.3 Reproducibility Proof

**First image build:**
```
$ sha256sum result
b6031c47c41dafbf8678b0db1355565b93794c6f50ec6ea9789f7ea8a0b7b23a result
```

**Second build (after `rm result`):**
```
$ nix-build docker.nix
/nix/store/bj3317qsim9d5l3b0hb43wqm841083mg-nix-app.tar.gz

$ sha256sum result
b6031c47c41dafbf8678b0db1355565b93794c6f50ec6ea9789f7ea8a0b7b23a result # identical hash
```

### 2.4 Comparison: Nix vs Traditional Docker

| Characteristic | Nix Image (`nix-app`) | Traditional (`test-app`) |
|---------------|----------------------|--------------------------|
| **Size** | 11.9 MB | ```850 MB |
| **Base Image** | scratch + minimal dependencies | golang:1.22 (full) |
| **Layers** | 3 deterministic | 12+ with timestamps |
| **`created` label** | `1970-01-01T00:00:01Z` (fixed) | Current build time |
| **Reproducibility** | Bit-for-bit identical hashes | Metadata hashes change |

**Conclusion:** `dockerTools` creates minimal images without timestamps or extra layers, ensuring full bit-for-bit reproducibility.

---

## Challenges and Solutions

| Problem | Solution |
|----------|---------|
| **CRLF line endings** | `dos2unix docker.nix default.nix` before building |
| **`vendorHash` error** | Set `vendorHash = null` for projects without external dependencies |
| **`result` is a file, not a directory** | Use `docker load < result`, not `result/nix-app.tar.gz` |
| **Paths with spaces in WSL** | Use quotes: `cd "/mnt/c/Users/.../lab11/app"` |

---

## Summary

Both tasks were completed successfully:

- **Task 1:** Built a Go application with Nix and verified bit-for-bit reproducibility across multiple builds. Store path and SHA-256 hash remained identical — proving that Nix eliminates environment-dependent variations.

- **Task 2:** Produced a minimal Docker image (11.9 MB) using `dockerTools`. Unlike traditional Docker builds, the Nix-built image uses fixed timestamps and deterministic layers, resulting in identical tarball hashes on every rebuild.

The key takeaway is that Nix enforces reproducibility at the build level: every input is explicitly declared, the build environment is sandboxed, and outputs are content-addressed. This eliminates the classic "works on my machine" problem — the same expression yields the same binary, on any machine, at any time.