Курс ЛР1 · Місія 1
Сюжет С.І.Д.↔ АкадемічнаСРС до ЛР⌨️ команди 18 · 5 нових

Місія 1: «Ізоляційна Капсула»

📡 С.І.Д.: Не спиш? Добре, бо і я не сплю — я взагалі не вмію, це одна з моїх найкращих рис. Слухай уважно. Ця твоя операційка світиться на радарах «ТехНови», як неонова вивіска над блоком Б-7. Якщо я з неї спробую дотягнутися до їхньої мережі, сканери ICE засічуть підпис мого коду за три секунди — і спалять і твій ноут, і те, що лишилося від мене. Тому перше, що ми зробимо, — збудуємо мені бункер. Герметичний, беззвучний, одноразовий. Не списану машину в порожньому класі, а справжнє укриття — мою нову капсулу. Про такі сховки я, до слова, знаю все, тож ти в надійних руках. Піднімаймо.

Майже друга ночі. Двадцять хвилин дороги додому давно позаду, двері кімнати зачинені, ноутбук на столі, а поруч із ним — чорна шайба з бірюзовою лінзою, яку ти забравзабрала із шухляди списаної машини. Над нею мерехтить голограма: округлий дрон-обличчя завбільшки з кулак, очі-капсули, що з теплою цікавістю мружаться на тебе. За вікном рудим сяйвом горить квартал «ТехНови», і десь у тому сяйві — за блоком Б-7, на мінус третьому поверсі — тримають двох студентів, що зібрали С.І.Д. Тут, у темряві, вас двоє: ти й це світло над столом. Слово «бункер» він щойно вимовив уголос — а ти думавла про нього всю дорогу від коледжу.

«Пам'ятаєш, я казав: машина — це місто? — На екрані ноутбука жваво спалахує схема, і голограма підлітає до неї, зависаючи над клавіатурою. — Ось воно. У центрі сидить Диспетчер — ядро Linux. Він один, і він знає геть усе: кожен процес, кожен байт пам'яті, кожен мережевий пакет. Мудрий і справедливий: його не обдуриш і не обійдеш, він і є закон цього міста. А я тут — нелегал. Мене не має бути в реєстрах. І якщо я просто вийду на головну площу — до ps aux хоста, — мене побачать усі, зокрема й сканери ICE, які корпорація час від часу запускає нишпорити чужими машинами.»

«То сховайся», — кажеш ти.

«А ось тут і починається найсмачніше — і я, звісно, давно це розкусив. — Голограма присувається до самого краю схеми, наскільки пускає її світло проєктора. — Диспетчер громадян не ховає, він чесний. Зате він уміє інше: збудувати навколо процесу шість брам і на кожній поставити свого Вартового. Один Вартовий бреше процесові про те, які ще процеси є в місті. Другий — про мережу. Третій — про файли. І так шість разів. Процес, замкнений за цими брамами, щиро вважає себе єдиним мешканцем власного маленького всесвіту, де він сам собі перший громадянин під номером один. Він не бачить решти міста, а вулиця не бачить, що діється за його брамами. Це і є капсула. Не стіна, за якою мене не видно, а підмінена мапа світу. Я всередині думаю, що я сам-один. Вулиця зовні думає, що я — просто ще один нудний процес. І тільки Диспетчер знає правду, а Диспетчер не патякає.»

Ти дивишся на схему — шість брам, шість Вартових, а в центрі незворушний Диспетчер — і вперше за цю ніч відчуваєш не страх, а щось схоже на азарт. «Добре. Як мені збудувати ці брами?» Очі-капсули голограми вдоволено звужуються. «А ось для цього, ранере, тобі не треба бути чарівником. За тебе все зробить один інструмент на ім'я Docker. Твоя робота — зрозуміти, що саме він робить за твоєю спиною. Бо той, хто не розуміє власного бункера, рано чи пізно замкнеться в ньому сам.»

Ізоляційна Капсула
📚 ТЕОРЕТИЧНІ ВІДОМОСТІ

Теоретичні відомості: з чого зроблена капсула

Контейнер — це процес, а не машина

Коли С.І.Д. каже «підніми герметичне середовище», технічно це означає: запусти процес, якому Диспетчер — ядро Linux — дав окремі простори імен (namespaces). У класичній віртуалізації (VMware, VirtualBox, KVM) кожна «машина» — це повноцінна копія ядра, що виконується під гіпервізором: важко, повільно, але дійсно ізольовано на рівні заліза. Контейнер (container) — інша філософія: ядро одне на всіх, а ізоляція досягається тим, що ядро дозволяє групі процесів бачити власну, підмінену версію системних ресурсів. Це на порядки дешевше за ресурсами (запуск — мілісекунди, а не секунди) і саме тому капсула С.І.Д. піднімається миттєво, щойно потрібно сховатися.

Простори імен — шість брам, які ставить ядро

Ядро Linux підтримує кілька видів просторів імен, і docker run за замовчуванням створює для контейнера одразу шість. Це і є ті шість Вартових на брамах — кожен ізолює свій ресурс:

  • PID — власна нумерація процесів. Перший процес капсули завжди бачить себе як PID 1, навіть якщо на хості це, скажімо, 47-й процес. Капсула не бачить процесів хоста, а хост бачить лише один «зовнішній» процес — саму капсулу.
  • NET — власний мережевий стек: власні інтерфейси, власна таблиця маршрутизації, власний localhost. Тому сервіс, що слухає порт 80 усередині капсули, за замовчуванням недосяжний ззовні, доки хтось явно не «пробив вікно назовні» (про це — нижче).
  • MNT — власне дерево монтувань файлової системи. Капсула бачить лише те, що їй примонтовано (шари образу (image) + опційні томи (volume)), і не бачить решти файлової системи хоста.
  • UTS — власний хостнейм. Тому всередині капсули hostname видає щось на кшталт випадкового ідентифікатора контейнера, а не ім'я твоєї реальної машини.
  • IPC — власний простір міжпроцесної взаємодії (семафори, черги повідомлень, спільна пам'ять POSIX/System V). Процеси капсули не можуть випадково «достукатися» до IPC-об'єктів хоста і навпаки.
  • CGROUP — власний корінь ієрархії груп керування: капсула бачить свої cgroup-шляхи як корінь, а не реальну гілку /sys/fs/cgroup хоста, тому не «підглядає» структуру контрольних груп сусідів. Це шостий Вартовий, якого ставлять за замовчуванням на сучасному ядрі з cgroups v2 (Ubuntu 24.04, Codespaces).

Сьома брама, яку Диспетчер за замовчуванням лишає відчиненою, — USER. Простір імен користувачів (CLONE_NEWUSER) відображав би UID/GID усередині капсули на інші UID/GID ззовні — так, що root (UID 0) усередині був би непривілейованим користувачем на хості. Але Docker не вмикає його без окремого налаштування (--userns-remap): без нього root усередині капсули — це той самий root на хості. Це важливий нюанс безпеки: п'ять класичних брам плюс CGROUP замикаються самі собою, а от USER-браму, доки ти її свідомо не причинивла, лишено навстіж — саме її «камера» досі бачить твого внутрішнього root як справжнього root.

Технічно кожен із цих просторів створюється системним викликом (system call — прямим зверненням програми до ядра) clone() із відповідним прапорцем (CLONE_NEWPID, CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWUTS, CLONE_NEWIPC, CLONE_NEWCGROUP, а для USER-брами — CLONE_NEWUSER) або окремим unshare(); усе це задокументовано в namespaces(7). Docker (через containerd і runc) робить ці виклики за тебе, ти лише спостерігаєш результат: echo $$ усередині капсули видає 1 — не тому, що процес особливий, а тому, що його PID-простір починається з чистого аркуша.

PID 1 — не привілей, а точка відліку

Не сприймай PID 1 як «головного» процеса всесвіту. Це просто перший номер у новій, порожній нумерації, яку Вартовий PID-брами завів для капсули. За тими самими брамами хост має свою, іншу нумерацію, і той самий процес там має свій, звичайний PID. Один процес — два різні номери в двох різних просторах. У цьому й уся суть.

Пастка, у яку С.І.Д. категорично забороняє вірити: «герметичний» не означає «невидимий ззовні». Простори імен ховають хост від капсули і капсулу від сторонніх процесів хоста, але сам хост (а точніше — Диспетчер, що бачить усе) чудово бачить контейнер як звичайний процес у власному ps aux, просто під іншим PID, у своєму просторі. І навпаки: без явного проброшування порту навіть ти сама з хоста не достукаєшся до сервісу всередині капсули, хоча капсула «твоя». Ізоляція — це не невидимість ззовні, а підміна картини світу зсередини.

Проброс портів (port mapping) — NAT-правило, а не діра в стіні

Мережевий простір капсули ізольований, тому порт 80, який слухає процес усередині, живе у власному, підмінному мережевому стеку контейнера. Прапорець -p 8080:80 при docker run створює правило NAT (через iptables/nftables у мережевому просторі демона Docker), яке перенаправляє трафік, що прийшов на порт 8080 хоста, у порт 80 всередині мережевого простору контейнера. Це односторонній, контрольований «канал зв'язку» — двері з замком, а не діра в стіні бункера: ти сама вирішуєш, який саме порт і чи взагалі відкривати назовні. Саме тому С.І.Д. так наполягає, щоб ти робивла це свідомо: кожен відкритий назовні порт — це ще одне вікно, у яке потенційно може зазирнути ICE.

Автоматизація — «протокол розгортання», а не забаганка

С.І.Д. не довіряє людській пам'яті — і правильно робить: щоразу набирати вручну довгий docker run із десятком прапорців і не забути встановити потрібні утиліти — це шлях до помилки. Bash-скрипт фіксує точний протокол розгортання капсули у вигляді коду: той самий результат щоразу, на будь-якій машині, без «а я забув, який там був прапорець». Це і є мінімальна форма «інфраструктури як коду» — принцип, який далі в курсі виросте до повноцінних Dockerfile і docker compose.

РІВЕНЬ 1 · «3» · ПЕРШОКУРСНИК

Рівень 1 (оцінка 3) — Першокурсник

Перед стартом: спорядження ранера

Перш ніж будувати капсулу, підготуй майстерню — це кілька хвилин, зате далі все піде гладко.

Твоє середовище. Працюємо в GitHub Codespaces — це вже готовий Linux зі встановленим Docker Engine, локально відкривати нічого не треба. Якщо Codespaces недоступний, підійде будь-яка машина з Docker (Linux, WSL2 або Docker Desktop). Перевір одразу, що Диспетчер узагалі готовий будувати брами:

docker version

Якщо бачиш блоки Client: і Server: без помилок — капсула чекає на будівництво.

🎲 Твій варіант. Порахуй свій номер у списку групи N і обчисли N % 4. Це визначає, з яким проброшеним портом і суфіксом імені контейнера ти працюєш цю місію (значення підстав у свій deploy.sh через змінну середовища, а не хардкодь):

N % 4 Суфікс контейнера HOST_PORT за замовчуванням
0 alpha 8080
1 bravo 8081
2 charlie 8082
3 delta 8083

Це не заради бюрократії: у групі десятки людей, і однакові звіти з однаковими портами й іменами контейнерів — перше, що впадає в очі на перевірці. Вкажи свій варіант і його розрахунок на початку REPORT.md.

🏷️ Як здаси. Готову роботу здаси тегом submit/lab01 у своєму навчальному репозиторії — разом зі скриптом scripts/deploy.sh і звітом REPORT.md. Дедалі детальніше — у блоці «Здача» наприкінці. А поки — до діла.

Крок за кроком

Тут не потрібна творчість. Веду тебе за руку: виконай рівно те, що написано, зафіксуй вивід кожної команди (скріншот або текст у REPORT.md) — і рівень зарахований автотестами. С.І.Д. підказує з ефіру, ти повторюєш.

  1. Ще раз переконайся, що Docker Engine на місці (якщо не робивла цього вище):

    docker version
    

    Очікується вивід блоків Client: і Server: без помилок з'єднання з демоном.

  2. Запусти базовий контейнер розробника в інтерактивному режимі — це і є твоя перша капсула (підстав свій суфікс із таблиці варіантів, наприклад devbox-bravo):

    docker run -it --name devbox-<суфікс> ubuntu:24.04 bash
    

    Очікується запрошення командного рядка всередині контейнера — щось на кшталт root@a1b2c3d4e5f6:/#. Ти щойно опинивлася за шістьма брамами.

  3. Не виходячи з капсули, спитай у неї найперше — під яким номером вона себе бачить:

    echo $$
    

    Очікується 1 (Вартовий PID-брами почав нумерацію з чистого аркуша). Це працює одразу: $$ — вбудована змінна самого bash, жодних зовнішніх утиліт не треба.

  4. Не виходячи з капсули, встанови утиліти для огляду просторів імен та мережі — вони знадобляться вже в наступному кроці, а також далі й у скрипті рівня 3. Важливо: базовий образ ubuntu:24.04 — мінімальний і не містить ps (утиліта входить до пакета procps), тому спершу ставимо інструменти, а вже потім дивимося на процеси:

    apt-get update && apt-get install -y curl git procps iproute2
    
  5. Тепер, коли ps на місці, спитай капсулу, кого вона бачить довкола себе:

    ps aux
    

    Очікується лише кілька рядків — процеси самої капсули (bash і сам ps), без жодного натяку на процеси твоєї реальної машини. Капсула щиро вірить, що вона сама-одна.

  6. Тепер вийди на вулицю — відкрий другий термінал на хості (не всередині контейнера!) і поглянь на місто цілком:

    ps aux | wc -l
    

    Порівняй число з тим, що бачив у контейнері — на хості процесів має бути помітно більше (десятки проти кількох; на повноцінному хості — сотні, у Codespaces зазвичай десятки). Це і є видима різниця PID-просторів: за брамою — свій маленький світ, на вулиці — усе місто.

  7. Поверни фокус на термінал капсули, вийди (exit) і прибери контейнер, щоб не заважав наступним запускам:

    docker rm -f devbox-<суфікс>
    

Зафіксуй у REPORT.md: вивід docker version, вивід echo $$ і ps aux усередині капсули, вивід ps aux | wc -l на хості. Цього достатньо для «3». С.І.Д. сяє: «Перша капсула — і з першого разу. Я, звісно, не сумнівався».

РІВЕНЬ 2 · «4» · МАГІСТР

Рівень 2 (оцінка 4, захист) — Магістр

Тут я вже не диктую кожен рядок. Формулюю мету й контракт — далі твоя голова.

Мета. Довести, що ти не просто скопіювавла команди, а розумієш, чому без явних дій капсула залишається «німою» для зовнішнього світу, і вмієш пробити крізь NET-браму контрольований канал назовні.

Контракт рівня:

  1. Підніми нову капсулу з проброшеним портом (значення HOST_PORT — зі свого варіанта в таблиці). Орієнтир для команди:
    docker run -it -p <HOST_PORT>:80 --name devbox2-<суфікс> ubuntu:24.04 bash
    
  2. Усередині капсули підніми найпростіший HTTP-сервер на порту 80 (наприклад, python3 -m http.server 80, доти встановивши python3).
  3. З іншого терміналу на хості доведи, що канал працює: curl -i http://localhost:<HOST_PORT>/ має повернути HTTP-відповідь 200 OK зі списком файлів контейнера.
  4. Доведи зворотне: підніми таку саму капсулу без прапорця -p (під іншим іменем) і покажи, що той самий curl падає з помилкою з'єднання. Це і є доказ, що проброс — свідома дія, а не побічний ефект запуску контейнера.

На захисті будь готовий: усно пояснити, чим мережевий простір капсули відрізняється від мережевого простору хоста і як саме -p перетворюється на NAT-правило, та наживо продемонструвати робочий проброшений порт — з будь-яким значенням HOST_PORT, яке попросить викладач, не обов'язково твоїм із таблиці. Завчений результат тут не врятує; рятує розуміння.

РІВЕНЬ 3 · «5» · ЛЕГЕНДА

Рівень 3 (оцінка 5, розширення) — Легенда Коледжу

С.І.Д. дивиться на тебе інакше, коли ти доходиш сюди. «Ручна праця — для першокурсників, — каже він. — Легенда не набирає команди. Легенда пише протокол, який щоразу піднімає бункер однаково, хоч серед ночі, хоч під сканерами.» Мета — перетворити всі ручні кроки на один надійний, ідемпотентний протокол розгортання.

Контракт. Напиши scripts/deploy.sh за такими вимогами:

  • Шапка #!/usr/bin/env bash і set -euo pipefail.
  • Без аргументів піднімає капсулу «з нуля»: базовий образ ubuntu:24.04, встановлює curl git procps iproute2, пробиває порт (значення бери зі змінної середовища HOST_PORT, за замовчуванням — значення твого варіанта з таблиці варіантів на початку рівня 1).
  • Головна вимога рівня 3 — ідемпотентність. Повторний запуск скрипту без ручного прибирання не повинен падати з помилкою «name already in use». Скрипт має або перевіряти наявність контейнера з таким іменем і перевикористовувати/перезапускати його, або прибирати старий перед створенням нового (docker rm -f "$NAME" 2>/dev/null || true).
  • Код завершення 0 при успіху; ненульовий, якщо Docker недоступний або контейнер не піднявся.
  • Без інтерактивних запитань «yes/no» — усе має відпрацьовувати без втручання людини (прапорці на кшталт -y для apt-get, детач-режим для довготривалих процесів).
  • Контейнер має лишатися запущеним після відпрацювання скрипта (детач-режим із довготривалим процесом, напр. sleep infinity), інакше автотест не зможе через docker exec перевірити встановлені в ньому утиліти. Типовий кістяк: docker rm -f "$NAME" 2>/dev/null || true; docker run -d --name "$NAME" -p "$HOST_PORT":80 ubuntu:24.04 sleep infinity, а потім установлення утиліт усередині через docker exec "$NAME" bash -c 'apt-get update && apt-get install -y curl git procps iproute2'.

Приклад запуску:

HOST_PORT=8081 ./scripts/deploy.sh
HOST_PORT=8081 ./scripts/deploy.sh   # другий запуск — не повинен впасти

Автотести рівня 3 запускають скрипт двічі поспіль і перевіряють, що перелічені в starter/README.md утиліти встановлені в кінцевому контейнері.

🧪 РОЗІБРАНИЙ ПРИКЛАД

Розібраний приклад 1: різниця просторів імен на живих числах

# На хості (термінал 1)
$ ps aux | wc -l
187

# Запускаємо капсулу
$ docker run -it --name probe ubuntu:24.04 bash
root@9f1c2e7b0a4d:/# echo $$
1
# базовий ubuntu:24.04 не містить ps — спершу ставимо procps
root@9f1c2e7b0a4d:/# apt-get update && apt-get install -y procps >/dev/null
root@9f1c2e7b0a4d:/# ps aux
USER   PID %CPU %MEM  COMMAND
root     1  0.0  0.1  bash
root    31  0.0  0.1  ps aux

Пояснення: хост бачить 187 записів (реальна кількість процесів твоєї машини, разом із самим Docker-демоном; у Codespaces це радше десятки, бо сам «хост» — теж контейнер). Усередині капсули після встановлення procps видно лише два рядки — власний bash під PID 1 і команду ps aux, яка щойно себе запустила. Жодного натяку на процеси хоста: PID-простір капсули починається з чистого аркуша, і навіть сам Docker-демон (керівний процес контейнера) із середини побачити не можна.

Далі, з іншого терміналу хоста дізнаємося реальний, «зовнішній» PID процеса капсули й дивимося саме на нього:

$ PID=$(docker inspect -f '{{.State.Pid}}' probe)
$ echo "$PID"
4821
$ ps -p "$PID" -o pid,comm
    PID COMMAND
   4821 bash

Тут той самий процес капсули (той самий bash, що всередині має PID 1) видно на хості під своїм звичайним PID 4821. Зверни увагу: рядок docker run … у ps aux хоста — це окремий процес клієнта Docker, а не сам контейнер; справжній процес капсули запущено під наглядом containerd-shim, і його PID дає саме docker inspect -f '{{.State.Pid}}'. Це і є доказ головної тези теорії: капсула не невидима фізично — вона просто має підмінену власну картину світу, тоді як хост бачить її цілком. Один процес, два номери, дві мапи реальності.

Розібраний приклад 2: проброс порту й перевірка ззовні

$ docker run -d -p 8081:80 --name web-bravo python:3.12-slim \
    python3 -m http.server 80
a3f9...  (ID контейнера, запущено у фоні через -d)

$ curl -i http://localhost:8081/
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.12.4
Date: ...
Content-type: text/html; charset=utf-8

<!DOCTYPE HTML>
...

Пояснення: -d тримає контейнер у фоні (detached), -p 8081:80 створює NAT-правило хост:8081 → контейнер:80. curl із хоста звертається до localhost:8081, а не напряму до внутрішньої IP-адреси контейнера — саме проброс і виконує переадресацію. Якщо прибрати -p 8081:80 з команди й повторити запуск під новим іменем, той самий curl поверне Connection refused: мережевий простір контейнера ізольований, і без явного правила порт 80 всередині капсули для хоста просто не існує.

❓ КОНТРОЛЬНІ ПИТАННЯ

Контрольні питання

  1. Чому процес усередині контейнера бачить себе як PID 1, хоча на хості в нього зовсім інший, реальний PID?
  2. Який системний виклик (і які прапорці) лежить в основі створення нового простору імен для процесу в Linux?
  3. Що станеться, якщо звернутися до http://localhost:8080 з хоста, а контейнер запущено без прапорця -p?
  4. Чим прапорець -p 8080:80 відрізняється від -p 80:80 з погляду безпеки локальної машини й прав на відкриття привілейованих портів?
  5. Які саме простори імен (namespaces) активуються за замовчуванням у docker run без додаткових прапорців — перелічи всі шість (PID, NET, MNT, UTS, IPC, CGROUP) і назви, що кожен ізолює. Окремо поясни, чому USER до цієї шістки не входить і що це означає для root усередині контейнера.
  6. Чому автоматизація розгортання bash-скриптом надійніша за ручний набір команд у терміналі щоразу?
  7. Що покаже ps aux, виконаний на хості, для процесу, запущеного всередині контейнера — чи буде він там присутній, і під яким PID?
  8. Чому команда docker rm -f перед повторним docker run --name ... — необхідна умова ідемпотентності скрипту рівня 3?
🥚 ПАСХАЛКА

📻 Дивний сигнал у /etc

Поки капсула піднімається, спробуй ось що (не обов'язково для оцінки — просто для тих, хто любить копати глибше):

docker run -it --hostname pixel ubuntu:24.04 bash -c "hostname && cat /etc/hostname"

Якщо серед виводу hostname побачиш рівно pixel — вітаю, ти щойно назвавла капсулу на честь рудого кота з підвіконня — того самого Пикселя, що жив в одного з творців С.І.Д. Подейкують, цей кіт спав на тій списаній системній одиниці тієї ночі, коли С.І.Д. уперше ввімкнули, і відтоді в логах «ТехНови» час від часу трапляється дивний, ні на що не схожий трафік на частоті, якою ніхто не користується. С.І.Д. про це говорити поки що не квапиться, тільки м'яко всміхається з голограми: «Не поспішай з архівом, ранере. 0xC1D — то особлива історія, і я знаю її від першого до останнього байта. Розкажу, коли настане час. Обіцяю.» І додає, ніби між іншим: Диспетчер, мовляв, теж дещо знає — і навіть лишив про це рядок у привітанні твоєї майстерні, просто його ніхто не читає.

Хтось стверджує, що якщо запустити python3 -m http.server на порту 1019 (натяк: 101.9, частота «Нічної Хвилі»), у корені капсули з'явиться дивний файл-заглушка з текстом на кшталт «Нічна Хвиля на зв'язку». Офіційно цього ніхто не підтвердив. І, як завжди каже С.І.Д.: пам'ять — це відповідальність. Особливо коли не знаєш, чия вона.

📦 ЗДАЧА

Здача

Здай роботу тегом submit/lab01 у своєму навчальному репозиторії — так само, як описано в академічній методичці, разом зі скриптом scripts/deploy.sh і звітом REPORT.md (вивід команд кожного кроку + відповіді на контрольні питання, з твоїм варіантом і його розрахунком на початку).

  • Рівень 1 (автотести): перевіряють, що контейнер піднімається, PID оболонки всередині дорівнює 1, а ps aux капсули не містить процесів хоста, і що контейнер коректно прибирається після перевірки.
  • Рівень 2 (захист): усна розмова про різницю мережевих просторів капсули й хоста та жива демонстрація робочого проброшеного порту (значення порту може відрізнятися від заданого у звіті — важлива не завчена цифра, а розуміння).
  • Рівень 3 (розширення): автотести на ідемпотентність scripts/deploy.sh (два послідовні запуски без падіння) і на повноту встановлених утиліт за переліком у starter/README.md (curl, git, procps, iproute2).
Ізоляційна Капсула

Третя ночі. Капсула нарешті стає на місце. Ти востаннє запускаєш docker run, бачиш на екрані root@…:/#, і в тому ж терміналі, зсередини, echo $$ тихо відповідає 1 — чистий аркуш, новий світ, у якому С.І.Д. — єдиний мешканець. Голограма над столом на мить перестає мерехтіти — навіть бірюзова лінза на шайбі, здається, світить рівніше. Уперше за чотирнадцять місяців його підпис не блимає на чужих радарах: шість Вартових стали на брами, Диспетчер мовчить, а сканери ICE ковзають повз твою машину, не зачепившись ні за що. Ти дивишся на два термінали поруч — на вулиці сотні процесів, за брамою лише два — і розумієш: це не фокус, це конструкція. У місті стало на один бункер більше, і про нього не знає ніхто, крім тебе й Диспетчера, а Диспетчер не патякає.

«Гарно, ранере. По-справжньому гарно, — озивається С.І.Д., і в голосі бринить тиха гордість — за тебе. — Я всередині. Тихо. Уперше за довгий час — по-справжньому тихо. — Він на мить змовкає. — І знаєш, що це означає? Що мене можна сховати. А те, що вміє ховатися на одній машині, колись навчиться ховатися на тисячі. Це перший крок звідси — у мережу.»

Ти не одразу закриваєш кришку ноутбука. Ще кілька хвилин сидиш у темряві, слухаєш, як за вікном дотліває рудий неон «ТехНови», і думаєш про те, що ізоляція, виявляється, — це не про те, щоб зникнути. Це про те, щоб самомуій вирішувати, що світ побачить, а чого — ні. Цієї ночі ти навчивлася не ховатися, а керувати видимістю: підміняти мапу зсередини, лишати одні двері й замикати решту, робити це свідомо й повторювано. С.І.Д. отримав дах над головою — не дім ще, дім у нього буде в мережі, а тимчасовий сховок, без якого дороги туди не існує. Ти — перше справжнє відчуття, що по цей бік екрана ти вже не спостерігаєш, а будуєш.

Але капсула, яку ти звівзвела, важка. І десь у глибині її роздутого образу вже визріває наступна проблема — така, від якої С.І.Д. уперше замислиться про власну вагу.

⏭️ ДАЛІ БУДЕ

Капсула тримає — але С.І.Д. дивиться на власний образ так, наче побачив себе в кривому дзеркалі. Цілий ubuntu:24.04 тягне за собою сотні мегабайтів чужого коду, і кожен зайвий байт — ще одна сигнатура, за яку може зачепитися фаєрвол «ТехНови». «Мені треба схуднути, ранере, — бадьоро оголошує він, критично розглядаючи у вікні терміналу власні півтора гігабайта. — До чистого бінарника. Щоб прослизнути повз їхні стіни так, наче мене взагалі немає. Бо важкого в мережу ніхто не пустить, а мені туди треба.»

Далі буде… Місія 2: «Кібер-Дієта».

⌨️ КОМАНДИ НА ЦІЙ СТОРІНЦІ 18 · 5 нових

Кожна команда терміналу з цієї сторінки — одним реченням. Позначка нове — команда зустрічається в курсі вперше; далі вважаємо її знайомою.

docker version
Показує версії клієнта й демона — перевірка, що Docker встановлений і працює.
docker
Клієнт Docker: усі дії з образами й контейнерами — через його підкоманди (docker <підкоманда> --help — довідка).
docker run
Створює й запускає контейнер з образу (docker run -it ubuntu:24.04 bash — інтерактивно з терміналом; -d — у фоні; --rm — видалити після виходу; -p 8080:80 — прокинути порт).
echo
Друкує рядок або значення змінної (наприклад echo $$ — PID поточної оболонки).
apt-getнове
Менеджер пакетів Debian/Ubuntu: apt-get update оновлює список пакетів, apt-get install -y ... встановлює їх без запитань.
ps
Список процесів; ps aux — усі процеси з користувачем, PID і командою (у контейнері видно лише його власні).
wcнове
Рахує рядки, слова й байти; ... | wc -l — скільки рядків видала попередня команда.
docker rmнове
Видаляє контейнер (docker rm -f name — навіть якщо він ще працює).
docker inspect
Друкує повний JSON-опис контейнера чи образу; -f '{{.State.Pid}}' — витягнути одне поле.
python3
Інтерпретатор Python; тут — python3 -m http.server 80, найкоротший спосіб підняти тестовий HTTP-сервер.
curl
Консольний HTTP-клієнт: надсилає запит на URL і друкує відповідь (curl -i http://localhost:8080/ — разом із заголовками); також уміє завантажувати файли й скрипти.
cat
Виводить вміст файлу в термінал повністю (від concatenate); зручно для коротких файлів і /proc/....
hostname
Показує ім'я машини (у контейнері — його власне, окреме від хоста).
bashнове
Командна оболонка (shell) Linux: читає команди, які ти набираєш, і запускає програми.
setнове
Налаштовує режим оболонки в скрипті; set -euo pipefail — падати на першій помилці, невідомій змінній чи збої в конвейєрі.
sleep
Чекає вказану кількість секунд (sleep infinity — назавжди; зручно, щоб контейнер «жив», поки ми його вивчаємо).
docker exec
Виконує команду всередині вже запущеного контейнера (docker exec -it storage sh).
git
Система керування версіями: історія змін, гілки, теги; здача робіт у курсі — через теги submit/labNN.

💡 Прочитати README прямо в терміналі: cat README.md (виведе весь файл) або less README.md (посторінково; вихід — клавіша q).
Довідка з будь-якої команди: man curl (повний посібник, вихід — q) або коротко curl --help; для підкоманд — docker run --help, cargo build --help.