Перейти к содержимому

Практика 6. Docker: первые контейнеры


Цель работы

Установить Docker, освоить работу с образами и контейнерами, написать Dockerfile, научиться использовать volumes и сети.


Часть 1. Установка и первый запуск

Задание 1.1. Установка Docker

  1. Установите Docker (Ubuntu/Debian):

    Окно терминала
    sudo apt update
    sudo apt install -y docker.io
    sudo systemctl enable --now docker
  2. Добавьте текущего пользователя в группу docker (чтобы не использовать sudo):

    Окно терминала
    sudo usermod -aG docker $USER
    # Перезайдите в сессию или выполните:
    newgrp docker
  3. Проверьте установку:

    Окно терминала
    docker version
    docker info

Задание 1.2. Первый контейнер

  1. Запустите тестовый контейнер:

    Окно терминала
    docker run hello-world

    Прочитайте вывод и объясните каждый шаг, который выполнил Docker.

  2. Проверьте, что контейнер завершился:

    Окно терминала
    docker ps -a

    Какой статус у контейнера?

  3. Удалите контейнер:

    Окно терминала
    docker rm $(docker ps -aq --filter ancestor=hello-world)

Часть 2. Работа с образами

Задание 2.1. Поиск и загрузка

  1. Найдите образ Python на Docker Hub:

    Окно терминала
    docker search python
  2. Загрузите несколько вариантов образа Python:

    Окно терминала
    docker pull python:3.12
    docker pull python:3.12-slim
    docker pull python:3.12-alpine
  3. Сравните размеры:

    Окно терминала
    docker images | grep python

    Запишите размер каждого образа. Почему они так сильно отличаются?

Задание 2.2. Исследование слоёв

  1. Просмотрите историю (слои) образа:

    Окно терминала
    docker history python:3.12-slim

    Какие инструкции Dockerfile создали самые большие слои?

  2. Получите подробную информацию об образе:

    Окно терминала
    docker inspect python:3.12-slim | head -50

Задание 2.3. Управление образами

  1. Просмотрите все локальные образы:

    Окно терминала
    docker images
  2. Удалите ненужный образ:

    Окно терминала
    docker rmi python:3.12 # Удаляем самый тяжёлый
  3. Удалите неиспользуемые образы:

    Окно терминала
    docker image prune

Часть 3. Работа с контейнерами

Задание 3.1. Интерактивный режим

  1. Запустите Ubuntu-контейнер в интерактивном режиме:

    Окно терминала
    docker run -it --rm ubuntu:22.04 bash

    Внутри контейнера выполните:

    Окно терминала
    cat /etc/os-release
    whoami
    hostname
    ps aux
    ls /
    apt update && apt install -y curl
    curl http://example.com
    exit

    Что означает флаг --rm?

  2. Запустите Python-контейнер:

    Окно терминала
    docker run -it --rm python:3.12-slim python

    Выполните в интерпретаторе:

    import sys
    print(sys.version)
    print("Hello from Docker!")
    exit()

Задание 3.2. Фоновый режим и управление

  1. Запустите Nginx в фоне:

    Окно терминала
    docker run -d --name web -p 8080:80 nginx:alpine
  2. Проверьте, что контейнер работает:

    Окно терминала
    docker ps
    curl http://localhost:8080
  3. Просмотрите логи:

    Окно терминала
    docker logs web
    docker logs -f web # Следить в реальном времени (Ctrl+C для выхода)
  4. Выполните команду внутри работающего контейнера:

    Окно терминала
    docker exec -it web sh
    cat /etc/nginx/nginx.conf
    ls /usr/share/nginx/html/
    exit
  5. Просмотрите статистику ресурсов:

    Окно терминала
    docker stats web --no-stream
  6. Остановите и удалите контейнер:

    Окно терминала
    docker stop web
    docker rm web

Задание 3.3. Переменные окружения

Запустите PostgreSQL с переменными окружения:

Окно терминала
docker run -d --name testdb \
-e POSTGRES_USER=student \
-e POSTGRES_PASSWORD=secret123 \
-e POSTGRES_DB=testdb \
-p 5432:5432 \
postgres:16-alpine
# Проверьте подключение
docker exec -it testdb psql -U student -d testdb -c "SELECT version();"
# Очистка
docker stop testdb && docker rm testdb

Часть 4. Dockerfile

Задание 4.1. Простой Dockerfile для Python

  1. Создайте каталог для проекта:

    Окно терминала
    mkdir -p ~/docker-lab/app
    cd ~/docker-lab/app
  2. Создайте файл приложения app.py:

    from http.server import HTTPServer, SimpleHTTPRequestHandler
    import json
    import os
    from datetime import datetime
    class APIHandler(SimpleHTTPRequestHandler):
    def do_GET(self):
    if self.path == '/':
    response = {
    "message": "Hello from Docker!",
    "timestamp": datetime.now().isoformat(),
    "hostname": os.uname().nodename,
    "version": os.environ.get("APP_VERSION", "unknown")
    }
    self.send_response(200)
    self.send_header('Content-Type', 'application/json')
    self.end_headers()
    self.wfile.write(json.dumps(response, indent=2).encode())
    elif self.path == '/health':
    self.send_response(200)
    self.send_header('Content-Type', 'text/plain')
    self.end_headers()
    self.wfile.write(b"OK")
    else:
    self.send_response(404)
    self.end_headers()
    if __name__ == '__main__':
    port = int(os.environ.get('PORT', 8000))
    server = HTTPServer(('0.0.0.0', port), APIHandler)
    print(f"Server running on port {port}")
    server.serve_forever()
  3. Создайте Dockerfile:

    FROM python:3.12-slim
    LABEL maintainer="student@os-course"
    LABEL version="1.0"
    ENV APP_VERSION=1.0.0
    ENV PORT=8000
    WORKDIR /app
    COPY app.py .
    EXPOSE 8000
    RUN adduser --disabled-password --gecos "" appuser
    USER appuser
    CMD ["python", "app.py"]
  4. Создайте .dockerignore:

    __pycache__
    *.pyc
    .git
    .env
  5. Соберите и запустите:

    Окно терминала
    docker build -t myapp:1.0 .
    docker run -d --name myapp -p 8080:8000 myapp:1.0
    # Проверьте
    curl http://localhost:8080
    curl http://localhost:8080/health
    # Очистка
    docker stop myapp && docker rm myapp

Задание 4.2. Multi-stage build

  1. Создайте файл ~/docker-lab/go-app/main.go:

    package main
    import (
    "fmt"
    "net/http"
    "os"
    "time"
    )
    func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    hostname, _ := os.Hostname()
    fmt.Fprintf(w, "Hello from Go! Host: %s, Time: %s\n", hostname, time.Now().Format(time.RFC3339))
    })
    fmt.Println("Server starting on :8080")
    http.ListenAndServe(":8080", nil)
    }
  2. Создайте ~/docker-lab/go-app/go.mod:

    module go-app
    go 1.22
  3. Создайте multi-stage Dockerfile:

    # === Этап 1: сборка ===
    FROM golang:1.22-alpine AS builder
    WORKDIR /app
    COPY go.mod .
    COPY main.go .
    RUN CGO_ENABLED=0 GOOS=linux go build -o server .
    # === Этап 2: финальный образ ===
    FROM scratch
    COPY --from=builder /app/server /server
    EXPOSE 8080
    CMD ["/server"]
  4. Соберите и сравните размеры:

    Окно терминала
    cd ~/docker-lab/go-app
    docker build -t goapp:multi .
    docker images | grep -E "goapp|golang"

    Во сколько раз финальный образ меньше образа сборки?


Часть 5. Volumes

Задание 5.1. Named volumes

  1. Создайте том и запустите PostgreSQL:

    Окно терминала
    docker volume create pgdata
    docker run -d --name db \
    -e POSTGRES_USER=student \
    -e POSTGRES_PASSWORD=secret \
    -e POSTGRES_DB=testdb \
    -v pgdata:/var/lib/postgresql/data \
    -p 5432:5432 \
    postgres:16-alpine
  2. Создайте таблицу и вставьте данные:

    Окно терминала
    docker exec -it db psql -U student -d testdb -c "
    CREATE TABLE notes (id SERIAL PRIMARY KEY, text VARCHAR(255));
    INSERT INTO notes (text) VALUES ('Первая запись'), ('Вторая запись');
    SELECT * FROM notes;
    "
  3. Удалите контейнер и создайте заново:

    Окно терминала
    docker stop db && docker rm db
    docker run -d --name db \
    -e POSTGRES_USER=student \
    -e POSTGRES_PASSWORD=secret \
    -e POSTGRES_DB=testdb \
    -v pgdata:/var/lib/postgresql/data \
    -p 5432:5432 \
    postgres:16-alpine
  4. Проверьте, что данные сохранились:

    Окно терминала
    docker exec -it db psql -U student -d testdb -c "SELECT * FROM notes;"
  5. Очистка:

    Окно терминала
    docker stop db && docker rm db
    docker volume rm pgdata

Задание 5.2. Bind mounts

  1. Создайте HTML-файл на хосте:

    Окно терминала
    mkdir -p ~/docker-lab/html
    echo "<h1>Hello from bind mount!</h1>" > ~/docker-lab/html/index.html
  2. Запустите Nginx с bind mount:

    Окно терминала
    docker run -d --name web \
    -p 8080:80 \
    -v ~/docker-lab/html:/usr/share/nginx/html:ro \
    nginx:alpine
    curl http://localhost:8080
  3. Измените файл на хосте и проверьте:

    Окно терминала
    echo "<h1>Updated!</h1><p>$(date)</p>" > ~/docker-lab/html/index.html
    curl http://localhost:8080

    Изменения видны сразу без перезапуска контейнера!

  4. Очистка:

    Окно терминала
    docker stop web && docker rm web

Часть 6. Сети Docker

Задание 6.1. Пользовательская сеть

  1. Создайте сеть:

    Окно терминала
    docker network create mynet
    docker network ls
  2. Запустите два контейнера в одной сети:

    Окно терминала
    docker run -d --name server1 --network mynet nginx:alpine
    docker run -d --name server2 --network mynet nginx:alpine
  3. Проверьте связь между контейнерами по имени:

    Окно терминала
    docker exec server1 ping -c 3 server2
    docker exec server2 ping -c 3 server1

    DNS-имена работают автоматически в пользовательской сети!

  4. Просмотрите информацию о сети:

    Окно терминала
    docker network inspect mynet
  5. Очистка:

    Окно терминала
    docker stop server1 server2
    docker rm server1 server2
    docker network rm mynet

Мини-проект: контейнеризация веб-приложения

Создайте полноценное контейнеризированное приложение:

  1. Используйте Python-приложение из задания 4.1 (или напишите своё).
  2. Напишите Dockerfile с лучшими практиками:
    • Используйте slim-образ.
    • Создайте непривилегированного пользователя.
    • Оптимизируйте кэширование слоёв.
  3. Создайте .dockerignore.
  4. Соберите образ с тегом mywebapp:1.0.
  5. Запустите контейнер:
    • Порт 8080 на хосте → порт 8000 в контейнере.
    • Volume для данных (если есть).
  6. Проверьте работоспособность через curl.
  7. Просмотрите логи контейнера.
  8. Войдите внутрь контейнера через docker exec и исследуйте файловую систему.

Контрольные вопросы

  1. Чем контейнер отличается от виртуальной машины? Назовите 3 преимущества контейнеров.
  2. Какова роль namespaces и cgroups в контейнеризации?
  3. Что такое Docker-образ? Как устроена слоёная файловая система?
  4. Опишите жизненный цикл контейнера Docker.
  5. Чем CMD отличается от ENTRYPOINT в Dockerfile?
  6. Что такое multi-stage build? Когда он полезен?
  7. Чем Docker volume отличается от bind mount? Когда использовать каждый?
  8. Зачем нужны пользовательские Docker-сети? Чем они лучше сети по умолчанию?
  9. Что произойдёт с данными в контейнере, если его удалить без volume?
  10. Почему не рекомендуется запускать процессы в контейнере от root?