---
# System prepended metadata

title: Devops 2

---

## Zadanie 1

![Screenshot from 2025-10-16 21-01-57](https://hackmd.io/_uploads/SJxMz606lx.png)

Przygotowanie środowiska:

HOST:

```bash=
# pobieramy apt-cacher-ng i go konfigurujemy
sudo apt install apt-cacher-ng
sudo nano /etc/apt-cacher-ng/acng.conf
# Chcemy zmienić następujące rzeczy (głównie to odkomentować):
# - VerboseLog: 1
# - AllowUserPort: 80 443 3142
# - BindAddress: 0.0.0.0
```

Może jeszcze warto tylko sprawdzić jakie ip się ma 
```bash=
    ip a
```
![image](https://hackmd.io/_uploads/Hy1m0nJCge.png)

No i w teorii to tyle :DD

KLIENT:
To tak ja sobie postawiłem obraz który dostaliśmy na VirtualBoxie, tutaj jest konfiguracja sieci jaką zrobiłem. 
![image](https://hackmd.io/_uploads/ByKAT3yRxx.png)

Jak coś wszystko jest robione na koncie roota (login: root, hasło: root)

po odpaleniu oczywiście trzeba ogarnąć sieć
```bash=
ip link set up dev enp0s3
ip addr add <jakaś sieć z podsieci twojego ip> dev enp0s3
# sanity check
ping <ip_hosta>
```

i ja tutaj trochę robie okropny trick póki co (bo nie umiem po bożemu)
```bash=
nano /etc/apt/sources.list
# i powinna tam być 1 linijka
# https://deb.debian.org/(...)
# podmieniamy ją na 
# http://<ip_hosta>:3142/deb.debian.org/(...)
```

TODO: `etc/apt/apt.conf.d/01proxy` -> do apt.conf.d możemy wrzucać pliki jako patche na podstawową konfigurację, numeracja 2 cyfry (wedlug kolejnosci sa nakladane)

Ogl Nowak powiedział, że nasze też git -> jak kto woli

po tym wszystkim robimy 
```bash=
apt update

apt install firefox-esr


apt remove --purge firefox-esr
apt clean cache

apt install firefox-esr 
(powinniśmy zobaczyć że jest ładnie fetch)

```

HOST: 
szybki factcheck że wszystko działa (i sprawdzić że mieliśmy hity)
```bash=
ls -la /var/log/apt-cacher-ng/
# powinniśmy zobaczyć że zwiększa się rozmiar pliku apt-cacher.log
# Najlepiej to w ogóle odpalić
tail -f /var/log/apt-cacher-ng/apt-cacher.log
# inna możliwość jak np pobierzemy jakiś pakiet to sprawdzenie czy jest w cache'u
find /var/cache/apt-cacher-ng -name "*<nazwa_paczki>*.deb"
```

TODO: może zrobić to po bożemu

## Zadanie 2

![Screenshot from 2025-10-16 21-02-12](https://hackmd.io/_uploads/rkxGzpR6lx.png)

Dockerfile:
```dockerfile
FROM ubuntu:22.04

# Instalacja SSH i innych niezbędnych pakietów
RUN apt-get update && apt-get install -y \
    openssh-server \
    sudo ## To bym wywalił, bo chyba nie potrzebujemy? Z definicji raczej się nie dodaje sudo
    && mkdir /var/run/sshd # Ja bym to przeniósł do osobnego RUN

# Konfiguracja SSH
RUN echo 'root:password' | chpasswd
RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config

# Dodanie klucza publicznego
RUN mkdir -p /root/.ssh
COPY id_rsa.pub /root/.ssh/authorized_keys
RUN chmod 600 /root/.ssh/authorized_keys

# Ekspozycja portu SSH
EXPOSE 22

# Uruchomienie serwera SSH
CMD ["/usr/sbin/sshd", "-D"]
```

^ Uwaga do uwagi Grzesia -> to sudo chyba jest potrzebne, bo inaczej trzeba zrobic RUN chmod + odpowiednie uprawnienia do tych folderów

**Generowanie kluczy SSH:**
```bash
ssh-keygen -t rsa -b 4096 -f id_rsa -N ""
```

**Budowanie obrazu:**
```bash
docker build -t ssh-server .
```

**Uruchomienie kontenera:**
```bash
docker run -d -p 2222:22 --name ssh-container ssh-server
```

**Testowanie połączenia:**
```bash
ssh -i id_rsa -p 2222 root@localhost
```

**Testowanie trybu interaktywnego:**
```bash
docker run -it ssh-container /bin/bash
# W kontenerze uruchom: /usr/sbin/sshd -D
```
To mi nie działa, bo budowaliśmy ssh-server, więc u mnie śmiga tylko 
```bash
docker run -it ssh-server /bin/bash
```
+1 dla grzesia
**Sprawdzenie statusu kontenera:**
```bash
docker ps
docker ps --all
```

**Odpowiedzi na pytania:**
* kontener po wyjściu z trybu interaktywnego zostaje zatrzymany, ale nie usunięty
* `docker ps` pokazuje tylko działające kontenery
* `docker ps --all` pokazuje wszystkie


## Zadanie 3

![Screenshot from 2025-10-16 21-02-34](https://hackmd.io/_uploads/HJxkGfTApxx.png)

`Multi-stage build` w Dockerze to technika budowania obrazów, która pozwala na **oddzielenie środowiska kompilacji od środowiska wykonawczego**, co skutkuje **mniejszymi**, **bezpieczniejszymi** i **bardziej wydajnymi** obrazami końcowymi. Pozwala to na uniknięcie dodawania zbędnych artefaktów kompilacji, takich jak narzędzia developerskie, do finalnego obrazu. 

Jak to działa:
* W jednym pliku `Dockerfile` można użyć wielu instrukcji `FROM`, które definiują poszczególne etapy budowy. 
* Dane (np. skompilowany kod) są kopiowane z jednego etapu (np. etapu kompilacji) do innego (np. etapu produkcyjnego). 
* Finalny obraz jest tworzony tylko z ostatniego etapu, który zawiera tylko niezbędne do uruchomienia aplikacji pliki i zależności, co znacząco zmniejsza jego rozmiar. 

Korzyści:
* **Mniejsze obrazy:** Eliminacja zbędnych narzędzi kompilacji i artefaktów znacząco redukuje rozmiar obrazu. 
* **Większe bezpieczeństwo:** Mniejsze obrazy zawierają mniej potencjalnych luk w zabezpieczeniach. 
* **Lepsza wydajność:** Mniejsze obrazy szybciej się pobierają i uruchamiają. 
* **Łatwiejsze utrzymanie:** Proces budowy jest bardziej zorganizowany i łatwiejszy do zrozumienia. 

[Obszerny artykuł o multistage build](https://programistajava.pl/2025/01/01/co-to-jest-docker-multistage-build-i-jak-z-niego-korzystac/)
[Docsy](https://docs.docker.com/build/building/multi-stage/)

Izolowane środowisko budowania

Włączenie BuildKit:
```bash
export DOCKER_BUILDKIT=1
```

Użycie Docker Buildx:
```bash
# Sprawdzenie dostępnych builderów
docker buildx ls

# Tworzenie nowego buildera
docker buildx create --name mybuilder --use
```

Przykładowy Dockerfile z izolacją:
```dockerfile
# syntax=docker/dockerfile:1
FROM ubuntu:22.04 AS build

WORKDIR /app
COPY . .
RUN make build

FROM ubuntu:22.04 AS runtime
COPY --from=build /app/bin /usr/local/bin/
CMD ["/usr/local/bin/myapp"]
```

SKOWI VERSION <- Better:DD

Ale po co nam w ogóle ten multistage? No to taki ficzur, który pozwala wiele razy korzystać z `FROM`. Pewnie ktoś mógłby zapytać, "no dobra, ale po co mi to?". Otóż to pozwala tworzyć mniejsze obrazy. Tak jak w przykładzie poniżej, możemy zacząć od gcc:13 i wykonać jakieś ciężkie obliczenia (kompilacja cpp), a później przekowiować już gotową apkę dalej (już do czegoś co korzysta np. z lekkiego debiana). Gemini mówi coś co chyba ma sens ![image](https://hackmd.io/_uploads/SJKtEWUAll.png)
 

```dockerfile
FROM gcc:13 AS builder

WORKDIR /app
COPY main.cpp .
RUN g++ -static -o app main.cpp

FROM debian:bookworm-slim 
WORKDIR /app
COPY --from=builder /app/app .

CMD ["/app/app"]
```

main.cpp
```c++
#include <iostream>

int main() {
    std::cout << "Hello from Docker multistage build!" << std::endl;
    return 0;
}
```

Budowanie z izolacją:
```bash
docker buildx build --platform linux/amd64 -t my-app . --load
```

Runowanie:
```bash
docker run -it my-app
```

TODO: ktoś miał `copy <<EOF .... EOF` -> spoko rzecz


## Zadanie 4

![Screenshot from 2025-10-20 15-30-21](https://hackmd.io/_uploads/Hy99K2mRgg.png)

https://docs.docker.com/build/building/best-practices/

```dockerfile
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y curl

FROM alpine
ENV ADMIN_USER=mark
RUN echo $ADMIN_USER > ./mark
RUN unset ADMIN_USER
```

Problemy i nieoczekiwane zachowania:
* Wielokrotne instrukcje `RUN`: Każdy `RUN` tworzy nową warstwę, co zwiększa rozmiar obrazu.
* Przełączanie bazowych obrazów: Ubuntu jest zastępowany przez Alpine (tracimy `curl`'a z Ubuntu)
* Zmienne środowiskowe: unset `ADMIN_USER` nie usuwa zmiennej z poprzednich warstw.
* Brak optymalizacji cache warstw: `apt-get update` i `apt-get install` powinny być w jednej instrukcji. 
* Ze względu bezpieczeństwa -> przy installu możemy korzystać ze spamiętanego stanu `apt-get update`, więc będziemy korzystać z cache, zamiast wymusić update. (niezaktualizowane paczki + stare repozytoria)

Błędnie poprawiona wersja:
```dockerfile
FROM ubuntu:22.04
RUN apt-get update && \
    apt-get install -y curl

# Usuwa ubuntu (z curlem)
# FROM alpine:latest

# Jak używamy ENV, to tworzy się wartstwa, do której potem możemy się podłączyć
ENV ADMIN_USER=mark

# Wszystkie operacje w jednej warstwie
RUN echo $ADMIN_USER > ./mark && \
    unset ADMIN_USER
```

Poprawiona wersja:
```dockerfile
FROM ubuntu:22.04
RUN apt-get update && \
    apt-get install -y curl

# Usuwa ubuntu (z curlem)
# FROM alpine:latest

# Wszystkie operacje w jednej warstwie (BEZ ENVA)
RUN export ADMIN_USER="mark" && \  
    echo $ADMIN_USER > ./mark && \
    unset ADMIN_USER
```

TODO: Można ustawić na puste, można używać arg, 



## Zadanie 5

![Screenshot from 2025-10-20 15-30-03](https://hackmd.io/_uploads/S19qtnQAle.png)

<!-- 
Przygotowanie kluczy na hoście:

```bash
# Generowanie klucza prywatnego
openssl genrsa -out ca-key.pem 4096

# Generowanie certyfikatu CA
openssl req -new -x509 -days 365 -key ca-key.pem -out ca-cert.pem

# Generowanie klucza serwera
openssl genrsa -out server-key.pem 4096

# Generowanie żądania certyfikatu serwera
openssl req -new -key server-key.pem -out server.csr

# Podpisanie certyfikatu serwera przez CA
openssl x509 -req -days 365 -in server.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem
```

Dockerfile z bezpiecznym kopiowaniem certyfikatów:

```dockerfile
FROM nginx:alpine

# Tworzenie katalogu na certyfikaty
RUN mkdir -p /etc/nginx/ssl

# Kopiowanie certyfikatów (bez klucza prywatnego CA!)
COPY server-cert.pem /etc/nginx/ssl/
COPY server-key.pem /etc/nginx/ssl/

# Konfiguracja NGINX z SSL
COPY nginx.conf /etc/nginx/nginx.conf

EXPOSE 443
CMD ["nginx", "-g", "daemon off;"]
```

Konfiguracja NGINX (nginx.conf):

```nginx
events {}
http {
    server {
        listen 443 ssl;
        ssl_certificate /etc/nginx/ssl/server-cert.pem;
        ssl_certificate_key /etc/nginx/ssl/server-key.pem;
        
        location / {
            return 200 "Hello Secure World!";
        }
    }
}

JANOOOO ODJEBAŁEŚ, to wyżej nie jest zgodne z treścią xD
``` -->

Setup must have:
```
 sudo apt install docker-buildx
 ```

Najpierw na hoście
```bash=
openssl genrsa -out ca-key.pem 4096
openssl req -new -x509 -days 365 -key ca-key.pem -out ca-cert.pem -subj "/C=PL/ST=OK/L=WROCLAW/O=UWUR/OU=AHA/CN=X"
```

Dockerek :DD 
```dockerfile=
FROM ubuntu:latest

RUN apt-get update && \
    apt-get install -y openssl

RUN --mount=type=secret,id=ca_key \
    --mount=type=secret,id=ca_cert \
    openssl genrsa -out cert-key.pem 4096 && \
    openssl req -new -key cert-key.pem -out cert.csr -subj "/C=CZ/ST=/L=CZEWA/O=SKOWI_CORP/OU=/CN=SKOWI_CEO" && \
    openssl x509 -req -days 365 -in cert.csr -CA /run/secrets/ca_cert -CAkey /run/secrets/ca_key -CAcreateserial -out signed-cert.pem
```
Tutaj warto podkreślić, że mamy ten `--mount=type=secret` przez co jest bezpiecznie:D https://docs.docker.com/build/building/secrets/


build i run
```bash=

docker build -t zad5 --secret id=ca_key,src=./ca-key.pem --secret id=ca_cert,src=./ca-cert.pem .

docker run -it zad5
```

By sprawdzić certyfikat w kontenerze możemy odpalić
```bash=
openssl x509 -in signed-cert.pem -text -noout
```


## Zadanie 6

![Screenshot from 2025-10-20 15-29-54](https://hackmd.io/_uploads/r1gcqFhQ0el.png)

### `--cpuset-cpus`

Kiedy używać: Gdy chcemy przypisać kontener do konkretnych procesorów CPU.
Uwaga od Nowaka: Np jeśli mamy kartę sieciową, to chcemy mieć blisko, żeby nie przechodzić między maszynami (cache są połączone, dokładniej L3 i to jets wolne)

Przykład użycia:

```bash
# Uruchomienie kontenera na CPU 0 i 1
docker run -d --cpuset-cpus="0,1" --name limited-cpu nginx

# Ograniczenie do zakresu CPU
docker run -d --cpuset-cpus="0-3" --name range-cpu nginx
```

### `docker image ls --all`:

```bash
# Pokazuje wszystkie obrazy (również pośrednie)
docker image ls --all
```

### `docker image history`:
```bash
# Pokazuje historię budowania obrazu
docker image history nazwa_obrazu

# Z wyjściem w formacie JSON
docker image history --format json nazwa_obrazu
```

### `docker image inspect`:

```bash
# Szczegółowe informacje o obrazie
docker image inspect nazwa_obrazu

# Wyciągnięcie konkretnych informacji
docker image inspect --format='{{.Config.Cmd}}' nazwa_obrazu
```

## Zadanie 7

![Screenshot from 2025-10-20 15-29-39](https://hackmd.io/_uploads/ryxcqK2XCle.png)

Problem:
Skrypt smok.sh nie ma praw wykonywania i ścieżka w CMD jest nieprawidłowa.

Poprawiony Dockerfile:
```dockerfile
FROM ubuntu:latest

# Tworzenie użytkownika
RUN groupadd -r smok && useradd --no-log-init -r -g smok smok

# Kopiowanie skryptu
COPY smok.sh /home/smok/smok.sh

# Nadanie praw wykonywania
RUN chmod +x /home/smok/smok.sh

# Zmiana właściciela pliku
RUN chown smok:smok /home/smok/smok.sh

# Przełączenie na użytkownika smok
USER smok

# Uruchomienie skryptu z pełną ścieżką
CMD ["/home/smok/smok.sh"]
```

Poprawiony skrypt smok.sh:
```bash
#!/bin/bash
for i in {1..10}; do
    echo $i
    sleep 0.5
done
```
Testowanie:
```bash
# Budowanie obrazu
docker build -t fixed-smok .

# Uruchomienie
docker run --rm fixed-smok
```

można też tak z WORKDIRem <- ogólnie warto korzystać z tego `WORKDIR`, zdaje się, że to dobra praktyka:D
```dockerfile
(...)

WORKDIR /home/smok
RUN chmod +x ./smok.sh

USER smok
CMD ./smok.sh
```

To ja pokażę merge obu, ta końcówka to dobra praktyka, tak samo jak `WORKDIR`
```dockerfile=
FROM ubuntu:latest

# Tworzenie użytkownika
RUN groupadd -r smok && useradd --no-log-init -r -g smok smok

# Kopiowanie skryptu
COPY smok.sh /home/smok/smok.sh

# Zmiana folderu
WORKDIR /home/smok

# Dodanie uprawnień
RUN chmod +x ./smok.sh

USER smok
CMD ["./smok.sh"]
```

## Zadanie 8


![Screenshot from 2025-10-20 15-29-21](https://hackmd.io/_uploads/r159YhXRlg.png)


Problematyczny fragment:
```dockerfile
RUN dd if=/dev/zero of=largefile bs=1M count=100
RUN rm largefile
```
Problem: Każda instrukcja `RUN` tworzy warstwę. Plik jest usuwany w kolejnej warstwie, ale nadal istnieje w poprzedniej.

Rozwiązania:
Łączenie instrukcji:

```dockerfile
RUN dd if=/dev/zero of=largefile bs=1M count=100 && \
    rm largefile
```

Multi-stage build:

```dockerfile
FROM ubuntu:latest AS builder
RUN dd if=/dev/zero of=largefile bs=1M count=100
# Operacje na dużym pliku

FROM ubuntu:latest
COPY --from=builder /app/result /app/result
# Tylko potrzebne pliki są kopiowane
```

Użycie .dockerignore:

```
largefile
*.tmp
```

Minimalne obrazy bazowe:

```dockerfile
FROM alpine:latest  # zamiast ubuntu
# Alpine jest znacznie mniejszy
```

Czyszczenie cache w tej samej warstwie:

```dockerfile
RUN apt-get update && \
    apt-get install -y package && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
```

Sprawdzenie rozmiaru:
```bash
docker images
docker history nazwa_obrazu
```

Docker squash (plugin, teraz w dockerze, ale ma być z powrotem przeniesiony do pluginów) -> patrzy sobie na to co wszystkie wartstwy pozmieniały w systemie plików i squashuje je do jednej warstwy z tym co jest istotne (czyli wyrzuci rzeczy odpowiedzialne za stworzenie dużego niepotrzebnego pliku a potem go usunięcie)

---

INNE

Włączenie BuildKit:

```bash
export DOCKER_BUILDKIT=1
# Lub w pliku ~/.docker/config.json
```

Przykładowy Dockerfile z zaawansowanymi funkcjami:

```dockerfile
# syntax=docker/dockerfile:1
FROM alpine:latest AS builder

RUN apk add --no-cache git make
WORKDIR /src
COPY . .
RUN make build

FROM alpine:latest
WORKDIR /app
COPY --from=builder /src/bin/app .
CMD ["./app"]
```

Budowanie z BuildKit:

```bash
docker build -t buildkit-example .

# Z zaawansowanymi opcjami
docker build \
  --progress=plain \
  --no-cache \
  -t buildkit-example .
```

Dlaczego używać BuildKit? - Lepsza wydajność, cache, bezpieczeństwo, multi-stage build.