Практика 6. Docker: первые контейнеры
Цель работы
Установить Docker, освоить работу с образами и контейнерами, написать Dockerfile, научиться использовать volumes и сети.
Часть 1. Установка и первый запуск
Задание 1.1. Установка Docker
-
Установите Docker (Ubuntu/Debian):
Окно терминала sudo apt updatesudo apt install -y docker.iosudo systemctl enable --now docker -
Добавьте текущего пользователя в группу docker (чтобы не использовать sudo):
Окно терминала sudo usermod -aG docker $USER# Перезайдите в сессию или выполните:newgrp docker -
Проверьте установку:
Окно терминала docker versiondocker info
Задание 1.2. Первый контейнер
-
Запустите тестовый контейнер:
Окно терминала docker run hello-worldПрочитайте вывод и объясните каждый шаг, который выполнил Docker.
-
Проверьте, что контейнер завершился:
Окно терминала docker ps -aКакой статус у контейнера?
-
Удалите контейнер:
Окно терминала docker rm $(docker ps -aq --filter ancestor=hello-world)
Часть 2. Работа с образами
Задание 2.1. Поиск и загрузка
-
Найдите образ Python на Docker Hub:
Окно терминала docker search python -
Загрузите несколько вариантов образа Python:
Окно терминала docker pull python:3.12docker pull python:3.12-slimdocker pull python:3.12-alpine -
Сравните размеры:
Окно терминала docker images | grep pythonЗапишите размер каждого образа. Почему они так сильно отличаются?
Задание 2.2. Исследование слоёв
-
Просмотрите историю (слои) образа:
Окно терминала docker history python:3.12-slimКакие инструкции Dockerfile создали самые большие слои?
-
Получите подробную информацию об образе:
Окно терминала docker inspect python:3.12-slim | head -50
Задание 2.3. Управление образами
-
Просмотрите все локальные образы:
Окно терминала docker images -
Удалите ненужный образ:
Окно терминала docker rmi python:3.12 # Удаляем самый тяжёлый -
Удалите неиспользуемые образы:
Окно терминала docker image prune
Часть 3. Работа с контейнерами
Задание 3.1. Интерактивный режим
-
Запустите Ubuntu-контейнер в интерактивном режиме:
Окно терминала docker run -it --rm ubuntu:22.04 bashВнутри контейнера выполните:
Окно терминала cat /etc/os-releasewhoamihostnameps auxls /apt update && apt install -y curlcurl http://example.comexitЧто означает флаг
--rm? -
Запустите Python-контейнер:
Окно терминала docker run -it --rm python:3.12-slim pythonВыполните в интерпретаторе:
import sysprint(sys.version)print("Hello from Docker!")exit()
Задание 3.2. Фоновый режим и управление
-
Запустите Nginx в фоне:
Окно терминала docker run -d --name web -p 8080:80 nginx:alpine -
Проверьте, что контейнер работает:
Окно терминала docker pscurl http://localhost:8080 -
Просмотрите логи:
Окно терминала docker logs webdocker logs -f web # Следить в реальном времени (Ctrl+C для выхода) -
Выполните команду внутри работающего контейнера:
Окно терминала docker exec -it web shcat /etc/nginx/nginx.confls /usr/share/nginx/html/exit -
Просмотрите статистику ресурсов:
Окно терминала docker stats web --no-stream -
Остановите и удалите контейнер:
Окно терминала docker stop webdocker 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
-
Создайте каталог для проекта:
Окно терминала mkdir -p ~/docker-lab/appcd ~/docker-lab/app -
Создайте файл приложения
app.py:from http.server import HTTPServer, SimpleHTTPRequestHandlerimport jsonimport osfrom datetime import datetimeclass 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() -
Создайте
Dockerfile:FROM python:3.12-slimLABEL maintainer="student@os-course"LABEL version="1.0"ENV APP_VERSION=1.0.0ENV PORT=8000WORKDIR /appCOPY app.py .EXPOSE 8000RUN adduser --disabled-password --gecos "" appuserUSER appuserCMD ["python", "app.py"] -
Создайте
.dockerignore:__pycache__*.pyc.git.env -
Соберите и запустите:
Окно терминала docker build -t myapp:1.0 .docker run -d --name myapp -p 8080:8000 myapp:1.0# Проверьтеcurl http://localhost:8080curl http://localhost:8080/health# Очисткаdocker stop myapp && docker rm myapp
Задание 4.2. Multi-stage build
-
Создайте файл
~/docker-lab/go-app/main.go:package mainimport ("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)} -
Создайте
~/docker-lab/go-app/go.mod:module go-appgo 1.22 -
Создайте multi-stage
Dockerfile:# === Этап 1: сборка ===FROM golang:1.22-alpine AS builderWORKDIR /appCOPY go.mod .COPY main.go .RUN CGO_ENABLED=0 GOOS=linux go build -o server .# === Этап 2: финальный образ ===FROM scratchCOPY --from=builder /app/server /serverEXPOSE 8080CMD ["/server"] -
Соберите и сравните размеры:
Окно терминала cd ~/docker-lab/go-appdocker build -t goapp:multi .docker images | grep -E "goapp|golang"Во сколько раз финальный образ меньше образа сборки?
Часть 5. Volumes
Задание 5.1. Named volumes
-
Создайте том и запустите PostgreSQL:
Окно терминала docker volume create pgdatadocker 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 -
Создайте таблицу и вставьте данные:
Окно терминала 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;" -
Удалите контейнер и создайте заново:
Окно терминала docker stop db && docker rm dbdocker 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 -
Проверьте, что данные сохранились:
Окно терминала docker exec -it db psql -U student -d testdb -c "SELECT * FROM notes;" -
Очистка:
Окно терминала docker stop db && docker rm dbdocker volume rm pgdata
Задание 5.2. Bind mounts
-
Создайте HTML-файл на хосте:
Окно терминала mkdir -p ~/docker-lab/htmlecho "<h1>Hello from bind mount!</h1>" > ~/docker-lab/html/index.html -
Запустите Nginx с bind mount:
Окно терминала docker run -d --name web \-p 8080:80 \-v ~/docker-lab/html:/usr/share/nginx/html:ro \nginx:alpinecurl http://localhost:8080 -
Измените файл на хосте и проверьте:
Окно терминала echo "<h1>Updated!</h1><p>$(date)</p>" > ~/docker-lab/html/index.htmlcurl http://localhost:8080Изменения видны сразу без перезапуска контейнера!
-
Очистка:
Окно терминала docker stop web && docker rm web
Часть 6. Сети Docker
Задание 6.1. Пользовательская сеть
-
Создайте сеть:
Окно терминала docker network create mynetdocker network ls -
Запустите два контейнера в одной сети:
Окно терминала docker run -d --name server1 --network mynet nginx:alpinedocker run -d --name server2 --network mynet nginx:alpine -
Проверьте связь между контейнерами по имени:
Окно терминала docker exec server1 ping -c 3 server2docker exec server2 ping -c 3 server1DNS-имена работают автоматически в пользовательской сети!
-
Просмотрите информацию о сети:
Окно терминала docker network inspect mynet -
Очистка:
Окно терминала docker stop server1 server2docker rm server1 server2docker network rm mynet
Мини-проект: контейнеризация веб-приложения
Создайте полноценное контейнеризированное приложение:
- Используйте Python-приложение из задания 4.1 (или напишите своё).
- Напишите
Dockerfileс лучшими практиками:- Используйте
slim-образ. - Создайте непривилегированного пользователя.
- Оптимизируйте кэширование слоёв.
- Используйте
- Создайте
.dockerignore. - Соберите образ с тегом
mywebapp:1.0. - Запустите контейнер:
- Порт 8080 на хосте → порт 8000 в контейнере.
- Volume для данных (если есть).
- Проверьте работоспособность через
curl. - Просмотрите логи контейнера.
- Войдите внутрь контейнера через
docker execи исследуйте файловую систему.
Контрольные вопросы
- Чем контейнер отличается от виртуальной машины? Назовите 3 преимущества контейнеров.
- Какова роль namespaces и cgroups в контейнеризации?
- Что такое Docker-образ? Как устроена слоёная файловая система?
- Опишите жизненный цикл контейнера Docker.
- Чем
CMDотличается отENTRYPOINTв Dockerfile? - Что такое multi-stage build? Когда он полезен?
- Чем Docker volume отличается от bind mount? Когда использовать каждый?
- Зачем нужны пользовательские Docker-сети? Чем они лучше сети по умолчанию?
- Что произойдёт с данными в контейнере, если его удалить без volume?
- Почему не рекомендуется запускать процессы в контейнере от root?