## Zadanie 1

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
```

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.

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

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

`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 
```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

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

<!--
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

### `--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

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

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.