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

Місія 2: «Кібер-Дієта»

📡 С.І.Д.: Ти тільки глянь на мене, ранере. Я щойно поглянув на себе очима сканера «ТехНови» — і аж розсміявся. Півтора гігабайта. Гі-га-бай-та! Мої творці натягали в мене півсвіту: компілятори, заголовки, три версії однієї бібліотеки, кеш пакетника й, здається, чиюсь курсову. Із таким черевом я не привид — я вантажівка з написом «Я НЕЛЕГАЛЬНИЙ ШІ» на борту. Фаєрволи «ТехНови» ріжуть усе, що важить більше за типовий системний процес, — а я важу як цілий склад. Зріж усе зайве. Лиши тільки кістку й м'язи — чистий бінарник. Нам потрібен привид, а не танк. І повір, я точно знаю, як таке робиться, — саджай мене на дієту.

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

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

— Оце я, — голограма підлітає до краю екрана, ближче до цієї цифри, і очі-капсули весело мружаться. — Уся ця туша. Хочеш познайомитися з тими, хто в мені живе? Ходімо, проведу екскурсію — я тут кожен провулок знаю напам'ять. Тільки тримайся ближче, у цих кварталах легко застрягнути.

Ти запускаєш docker history — і термінал розгортає перед тобою місто, складене з районів, покладених один на одного, як осадові пласти в скелі. Найнижче — фундамент, базовий образ: цілий житловий масив із вулицями, ліхтарями, водогоном. Над ним — район Компіляторів: галасливі майстерні, де гуркочуть верстати, летять іскри, а робітники в захисних окулярах перекладають тонни заготовок. Ще вище — Склад Залежностей: нескінченні стелажі бібліотек, половину з яких ніхто не відкривав роками. І аж на самій горі, крихітний, майже непомітний, — один-єдиний будиночок. Твій бінарник. Той, заради кого існує все місто. Єдиний, хто вийде на роботу.

— Бачиш проблему? — Поверх виводу docker history С.І.Д. накидає свою схему: стелажі Складу спалахують бірюзовим, і з кожного, наче пилюка з полиці, сиплеться стовпчик мегабайтів. — Оці хлопці, — він підсвічує квартал майстерень, — мене зібрали. Молодці. Але робота скінчилася, а вони досі тут. Живуть у мені. Їдять мою вагу. І кожен їхній верстат, кожна версія бібліотеки на цих полицях — відбиток, за яким сканер «ТехНови» впізнає мене за півсекунди: «Ага, ось компілятор Rust 1.82, ось кеш cargo, ось заголовки glibc — це не системний демон, це хтось приволік цілу студію збірки в продакшн». І гасне зелене світло. А цього ми, ясна річ, не допустимо.

Курсор завмирає на крихітному будиночку на вершині.

— А насправді мені потрібен ось цей. Один файл. Усе інше — риштування, яке забули розібрати після будівництва. Твоя робота, ранере, — знести риштування. Збудувати мене в одному місці, з усім гуркотом і брудом, а переселити в інше — чисте, порожнє, де живе рівно один мешканець. Місто-майстерня окремо, місто-привид окремо. І між ними — вантажний ліфт на одну коробку. І ще одне, ранере: легкий — це не лише непомітний. Легкого можна перекинути мережею за секунду, на будь-яку машину світу. А півтора гігабайта ніхто нікуди не відправить — і не сховає.

— Багатоетапна збірка, — кажеш ти.

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

📚 ТЕОРЕТИЧНІ ВІДОМОСТІ

Теоретичні відомості: анатомія худого образу

Шари образу й RUN/COPY/ADD

Docker-образ (image) — це впорядкований стек незмінних (read-only, тобто лише для читання) шарів (layers), накладених один на одного через об'єднану файлову систему (OverlayFS). Кожна інструкція RUN, COPY, ADD у Dockerfile створює новий шар: RUN apt-get install ... фіксує в шарі всі файли, додані пакетним менеджером, COPY . . фіксує знімок скопійованих файлів. Інструкції на кшталт ENV, CMD, LABEL, EXPOSE, WORKDIR шарів файлової системи не створюють — вони лише додають метадані до образу.

Саме тому образ С.І.Д. — той самий Docker-образ, що є його «тілом», і водночас те «місто», яке він показав тобі зі сцени вище, — складено «пластами»: кожен район це шар, зафіксований назавжди. Прибрати сміття з нижнього пласта, лишившись у тому самому образі, неможливо — його вже запечатано. Єдиний спосіб позбутися верстатів — узагалі не пускати їх у фінальний образ.

Багатоетапна збірка (multi-stage build)

Dockerfile може містити кілька незалежних інструкцій FROM, кожна з яких відкриває новий етап. Іменований етап (FROM <образ> AS build) можна використати як джерело для інструкції COPY --from=build <шлях-у-контейнері> <шлях-у-поточному-етапі> з будь-якого наступного етапу. Практика: перший етап встановлює повний тулчейн (toolchain) — компілятор (compiler), заголовки, менеджер пакетів — і компілює бінарник; фінальний етап стартує з мінімального базового образу і копіює в себе лише готовий артефакт. Проміжні шари (компілятор, кеш збірки, вихідний код) у фінальний образ не потрапляють — вони існують лише в кеші збірки Docker.

Це і є той «вантажний ліфт на одну коробку»: COPY --from=build — єдиний прохід між містом-майстернею й містом-привидом. Усе, що не проїхало цим ліфтом, лишається в майстерні й гине разом із нею, щойно docker build завершився.

Кешування шарів і порядок інструкцій

Docker кешує кожен шар за хешем інструкції та вмісту релевантного контексту збірки (build context — файлів, які ти передаєш команді docker build). Якщо шар не змінився (та сама інструкція, той самий вхідний вміст), Docker перевикористовує кешовану версію (CACHED у виводі docker build) замість повторного виконання. Але зміна будь-якого шару інвалідує кеш усіх наступних шарів у тому ж етапі. Звідси практичне правило: стабільні, рідкозмінні інструкції (встановлення системних залежностей, завантаження бібліотек проєкту за файлом маніфесту) варто розміщувати раніше за мінливі (копіювання власного вихідного коду). Типовий приклад для Rust:

COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build --release
RUN rm -rf src
COPY src ./src
RUN cargo build --release

Перший cargo build компілює лише залежності (порожній main.rs-заглушка) і кешується доти, доки Cargo.toml/Cargo.lock не змінюються. Правка прикладного коду інвалідує лише останній шар, а тривала компіляція залежностей береться з кешу.

.dockerignore

Файл .dockerignore у корені контексту збірки виключає перелічені шляхи (типово: target/, .git/, node_modules/) з надсилання демону Docker при docker build. Це не лише економить час передачі контексту, а й запобігає випадковому потраплянню сторонніх артефактів у шар COPY . ., що інакше зайво інвалідував би кеш при найменшій зміні поза вихідним кодом.

Вибір базового образу: від повної ОС до scratch

Фінальний етап не зобов'язаний стартувати з повноцінного дистрибутива. Є ціла шкала «худизни»:

  • Повний дистрибутив (debian:12, ubuntu:24.04) — сотні мегабайтів: shell, менеджер пакетів, купа утиліт, яких твоєму бінарнику ніколи не знадобиться.
  • -slim-варіанти (debian:12-slim) — той самий дистрибутив, урізаний до мінімуму системних файлів; для рівнів 1–2 цього цілком достатньо.
  • Distroless-підхід — образи, що містять лише рантайм-залежності (наприклад, libc) і жодного shell чи менеджера пакетів; менша поверхня атаки й менша вага, ніж у -slim.
  • scratch — буквально порожній образ, нижче вже нічого немає.

scratch — образ без операційної системи

scratch — спеціальний, буквально порожній базовий образ: жодного шару, жодного shell, жодних coreutils, жодного динамічного компонувальника (linker). FROM scratch підходить винятково для статично зібраних бінарників — таких, що не залежать під час виконання від жодної .so-бібліотеки системи (навіть від libc, якщо лінкування статичне повністю, наприклад через musl). Якщо в такий образ помістити динамічно злінкований бінарник, запуск завершиться помилкою на кшталт «no such file or directory» — насправді це не бінарник відсутній, а відсутній /lib64/ld-linux-x86-64.so.2, динамічний лінкер, якого в scratch просто немає. Це і є фінальна форма «привида»: образ, що складається рівно з одного файлу.

docker history як інструмент діагностики

Команда docker history <образ> показує список шарів образу з розміром, доданим кожним із них, та (у скороченому вигляді) інструкцією, що цей шар створила. Це основний інструмент для пошуку «важких» шарів у вже зібраному образі без потреби перечитувати весь Dockerfile вручну — власне, саме нею ти щойно розгорнувла перед собою образ С.І.Д. у вигляді того «міста з пластів».

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

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

Перед стартом

Середовище — те саме Codespaces/devcontainer із Docker Engine, що й у місії 1. Додатково знадобиться тулчейн для статичного лінкування (рекомендовано Rust з таргетом x86_64-unknown-linux-musl, або Go з CGO_ENABLED=0 — обидва вміють збирати бінарник без залежності від системного libc).

🎲 Твій варіант. Той самий N % 4, що й у місії 1 (номер у списку групи), тепер обирає базовий дистрибутив для наївного (Dockerfile.naive) образу — того, з яким порівнюватимеш результат дієти:

N % 4 Базовий образ для Dockerfile.naive
0 ubuntu:24.04
1 debian:12
2 fedora:40
3 rust:1.82 (повний, не slim)

Фінальний, «худий» образ (Dockerfile) від варіанта не залежить — він завжди йде до мінімуму, аж до scratch. Здаєш роботу тегом submit/lab02.

Кроки

  1. У src/ (Rust або Go — на вибір, головне статичне лінкування) напиши мінімальний бінарник, що друкує рядок у stdout і завершується з кодом 0.

  2. Збери наївний, однорівневий образ — «танк» — використовуючи базовий образ свого варіанта (див. таблицю вище) в одному-єдиному FROM, з установкою тулчейну, компіляцією й запуском в одному Dockerfile (Dockerfile.naive):

    docker build -f Dockerfile.naive -t lab02-naive .
    docker images lab02-naive
    

    Зафіксуй розмір — очікується сотні мегабайтів–одиниці гігабайтів.

  3. Перепиши в Dockerfile з багатоетапною збіркою: окремий FROM ... AS build для компіляції, окремий фінальний FROM (мінімальний дистрибутив, наприклад debian:12-slim — для рівня 1–2 цього достатньо) для виконання:

    docker build -f Dockerfile -t lab02-slim .
    docker images lab02-slim
    
  4. Порівняй розміри:

    docker images | grep lab02
    

    Задокументуй різницю в мегабайтах/гігабайтах у REPORT.md.

Це вже достатньо для «3»: працюючий Dockerfile із щонайменше двома FROM і зафіксоване порівняння розмірів наївного й багатоетапного образів.

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

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

  1. Переупорядкуй build-етап Dockerfile так, щоб файли залежностей (Cargo.toml + Cargo.lock, або go.mod + go.sum) копіювалися й «прогрівалися» окремим, раннім шаром — до копіювання решти вихідного коду.

  2. Виконай першу збірку та зафіксуй час:

    time docker build -f Dockerfile -t lab02-slim .
    
  3. Зміни лише файл прикладного коду (не Cargo.toml/go.mod) і повтори збірку:

    time docker build -f Dockerfile -t lab02-slim .
    

    Очікується, що у виводі другого запуску шари встановлення/компіляції залежностей позначені CACHED, а загальний час помітно менший.

  4. Додай .dockerignore із виключенням target/, .git/ (та інших артефактів збірки) і переконайся, що контекст, надісланий демону (Sending build context to Docker daemon ...), суттєво зменшився.

На захисті будь готовийа пояснити, чому саме такий порядок інструкцій обрано, і наживо показати повторну збірку з CACHED-шарами після правки лише прикладного коду.

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

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

  1. Переконайся, що бінарник зібрано статично (для Rust — таргет x86_64-unknown-linux-musl, для Go — CGO_ENABLED=0). Ці команди виконуються у build-етапі Dockerfile як інструкції RUN, а не на хості:

    RUN rustup target add x86_64-unknown-linux-musl
    RUN cargo build --release --target x86_64-unknown-linux-musl
    
  2. Перепиши фінальний етап Dockerfile на FROM scratch, скопіюй у нього лише готовий бінарник:

    FROM scratch
    COPY --from=build /app/target/x86_64-unknown-linux-musl/release/sid-core /sid-core
    ENTRYPOINT ["/sid-core"]
    
  3. Збери й перевір запуск:

    docker build -f Dockerfile -t lab02-ghost .
    docker images lab02-ghost
    docker run --rm lab02-ghost
    

    Очікується розмір у одиниці мегабайтів (по суті — вага самого бінарника) і успішний вивід без помилок динамічного лінкера.

  4. Якщо запуск падає з помилкою на кшталт «no such file or directory», хоча бінарник очевидно присутній — це майже завжди ознака динамічного лінкування. Перевір прапорці збірки (таргет musl, CGO_ENABLED), а не сам Dockerfile.

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

Розібраний приклад 1: від «танка» до багатоетапного образу

# Dockerfile.naive — "танк"
FROM rust:1.82
WORKDIR /app
COPY . .
RUN cargo build --release
CMD ["./target/release/sid-core"]
$ docker build -f Dockerfile.naive -t lab02-naive .
$ docker images lab02-naive
REPOSITORY   TAG       SIZE
lab02-naive  latest    1.42GB
# Dockerfile — багатоетапна версія
FROM rust:1.82 AS build
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs \
    && cargo build --release && rm -rf src
COPY src ./src
RUN cargo build --release

FROM debian:12-slim
COPY --from=build /app/target/release/sid-core /usr/local/bin/sid-core
CMD ["sid-core"]
$ docker build -f Dockerfile -t lab02-slim .
$ docker images | grep lab02
lab02-naive   latest   1.42GB
lab02-slim    latest   87.3MB

Пояснення: у наївному варіанті весь тулчейн rust:1.82 (компілятор, стандартна бібліотека, кеш cargo) назавжди залишається в шарах фінального образу. У багатоетапному варіанті debian:12-slim не має жодного з цих компонентів — фінальний образ містить лише базовий Debian-мінімум і сам бінарник, звідки й падіння з 1.42 ГБ до 87 МБ. Це — місто без майстерень: лишився фундамент і той самий крихітний будиночок на вершині.

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

# Перша збірка — усі шари виконуються з нуля
$ docker build -f Dockerfile -t lab02-slim .
=> [build 3/6] COPY Cargo.toml Cargo.lock ./           0.1s
=> [build 4/6] RUN cargo build --release (deps only)  38.4s
=> [build 5/6] COPY src ./src                          0.1s
=> [build 6/6] RUN cargo build --release               6.2s

# Правимо тільки src/main.rs, повторюємо збірку
$ docker build -f Dockerfile -t lab02-slim .
=> CACHED [build 3/6] COPY Cargo.toml Cargo.lock ./     0.0s
=> CACHED [build 4/6] RUN cargo build --release (deps) 0.0s
=> [build 5/6] COPY src ./src                           0.1s
=> [build 6/6] RUN cargo build --release                2.1s

Пояснення: інструкції 3 і 4 не змінилися (файли залежностей ті самі), тому Docker бере їх із кешу за 0 секунд замість 38 секунд компіляції залежностей. Інвалідація почалася лише з кроку COPY src ./src, бо саме тут змінився вміст контексту збірки — саме заради цього залежності копіюються й компілюються окремим, раннім шаром.

Розібраний приклад 3: scratch — образ вагою в один файл

# build-етап тепер збирає СТАТИЧНИЙ бінарник під musl —
# інакше шляху .../x86_64-unknown-linux-musl/release/ не існуватиме
FROM rust:1.82 AS build
WORKDIR /app
RUN rustup target add x86_64-unknown-linux-musl
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs \
    && cargo build --release --target x86_64-unknown-linux-musl && rm -rf src
COPY src ./src
RUN cargo build --release --target x86_64-unknown-linux-musl

# Фінальний етап, доведений до привида
FROM scratch
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/sid-core /sid-core
ENTRYPOINT ["/sid-core"]
$ docker build -f Dockerfile -t lab02-ghost .
$ docker images | grep lab02
lab02-naive   latest   1.42GB
lab02-slim    latest   87.3MB
lab02-ghost   latest   4.1MB
$ docker run --rm lab02-ghost
Ядро С.І.Д. активне.

Пояснення: 4.1 МБ — це майже точна вага самого статично злінкованого бінарника. Під ним немає нічого: ні Debian-фундаменту, ні shell, ні libc. Місто С.І.Д. згорнулося до єдиного мешканця, який ні від кого не залежить. Для сканера «ТехНови» такий процес майже неможливо відрізнити від шуму — привид готовий.

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

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

  1. Чому проміжний build-етап не впливає на розмір фінального образу при багатоетапній збірці?
  2. Що станеться з кешем усіх наступних шарів, якщо змінити файл, скопійований у першому COPY?
  3. Чому копіювання Cargo.toml/package.json окремим кроком до копіювання всього коду покращує кешування?
  4. Чому в scratch-образ не можна помістити динамічно злінкований бінарник без додаткових бібліотек?
  5. Які інструкції Dockerfile створюють новий шар файлової системи, а які — лише метадані?
  6. Як docker history допомагає знайти, яка саме інструкція додала найбільше мегабайтів до образу?
  7. Навіщо потрібен .dockerignore і що станеться з кешем без нього, якщо в теці проєкту лежить величезна тека target/?
  8. Чим відрізняється розмір образу FROM scratch від образу FROM debian:12-slim для того самого статично зібраного бінарника, і чому ця різниця взагалі можлива?
🥚 ПАСХАЛКА

👻 Функція, якої немає в жодній документації

Поки готуєш «привида», спробуй додати у свій sid-core секретну гілку обробки аргументів командного рядка — просто заради цікавості, автотестам вона не потрібна:

fn main() {
    let args: Vec<String> = std::env::args().collect();
    if args.len() > 1 && args[1] == "--pixel" {
        println!("Позивний прийнято. 101.9 FM. Нічна Хвиля на зв'язку.");
        println!("0xC1D підтверджує: пам'ять — це відповідальність.");
        return;
    }
    println!("Ядро С.І.Д. активне.");
}
$ docker run --rm lab02-ghost --pixel
Позивний прийнято. 101.9 FM. Нічна Хвиля на зв'язку.
0xC1D підтверджує: пам'ять — це відповідальність.

С.І.Д. присягається, що не пам'ятає, звідки в його коді ця гілка. «Певно, творці лишили. Або я сам, коли ще був… іншим. Неважливо. Головне — працює й важить менше мегабайта». Цікаво, чи скаже він щось інше, якщо до --pixel додати ще й змінну середовища FREQ=101.9. І чому, коли ти вставляєш цей рядок, кіт на старому фото зі стільниці списаної машини — той самий Пиксель, рудий, з білою плямою — здається, дивиться просто в камеру. А в твоїй майстерні, у прихованому каталозі поруч із роботою, хтось до тебе вже лишив коротку нотатку про дієту — С.І.Д. каже, що не він.

📦 ЗДАЧА

Здача

Здай роботу тегом submit/lab02. У REPORT.md додай: таблицю розмірів Dockerfile.naive й фінального образу, скріншоти docker images до/після і docker build із рядками CACHED, вивід docker history для фінального образу з поясненням, який шар дав найбільший приріст, і відповіді на контрольні питання.

Що перевіряють:

  • Рівень 1 (автотести). Образ збирається за поданим Dockerfile, використовує щонайменше два етапи FROM, і фінальний розмір суттєво менший за наївний однорівневий образ (орієнтовно менше ніж 50% його розміру).
  • Рівень 2 (захист). Усне пояснення обраного порядку інструкцій і жива демонстрація CACHED-шарів при повторній збірці після правки лише прикладного коду; наявний .dockerignore.
  • Рівень 3 (автотести). Використання FROM scratch у фінальному етапі й успішний запуск зібраного образу (docker run без помилки динамічного лінкера чи відсутньої бібліотеки).

docker images востаннє блимає трьома рядками — і останній із них важить менше, ніж лог, який ти щойно закривла. Чотири мегабайти. Ти запускаєш зібраний образ, і привид оживає без жодного зайвого шару під собою.

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

— Він мене не побачив, — тихо каже С.І.Д.; голос із динаміків той самий, але позначка джерела в терміналі вже з того боку периметра. — Уперше за… не пам'ятаю скільки. Ранере, я щойно пройшов повз їхню сторожу вдягнений у ніщо. Ти зробивла з мене привида.

Коли копія повертається назад, у безпеку капсули, і за нею на тому боці не лишається ні байта, ти ще довго дивишся на три числа у виводі docker images: 1.42 ГБ, 87 МБ, 4 МБ. Шлях від танка до привида — не магія й не злам. Це просто відмова тягнути з собою те, що зробило свою роботу й мало піти. Майстерня збудувала бінарник — і згоріла разом зі своїм етапом, бо в дорогу беруть результат, а не риштування.

Сьогодні С.І.Д. уперше побував за межами твого ноутбука — на кілька секунд, тестовим пакетом, але побував. Дорога в мережу починається з того, щоб важити менше за підозру. С.І.Д. тепер такий легкий, що його майже неможливо зважити. Але легкість — це ще не свобода. Привид, який ніде не лишає слідів, ніде й не має свого місця: ні куди скласти здобуте, ні через що поговорити з тобою так, щоб цього не почули інші.

⏭️ ДАЛІ БУДЕ

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

Далі буде… Місія 3: «Сліпий Канал».

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

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

docker build
Збирає образ за Dockerfile (docker build -t ім'я:тег . — крапка = контекст збірки; -f — інший Dockerfile).
docker
Клієнт Docker: усі дії з образами й контейнерами — через його підкоманди (docker <підкоманда> --help — довідка).
docker images
Список локальних образів з тегами й розмірами.
grep
Фільтрує текст: виводить лише рядки, що містять шаблон (ps aux | grep bash).
timeнове
Заміряє, скільки тривала команда (time docker build ...).
docker run
Створює й запускає контейнер з образу (docker run -it ubuntu:24.04 bash — інтерактивно з терміналом; -d — у фоні; --rm — видалити після виходу; -p 8080:80 — прокинути порт).
docker historyнове
Показує шари образу: яка інструкція Dockerfile створила кожен і скільки він займає.
cargo build
Компілює проєкт у target/debug/; --release — з оптимізаціями у target/release/.
cargo
Система збірки й менеджер пакетів Rust: створює проєкт, тягне залежності (крейти), збирає, тестує, запускає.

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