Эта лабораторная работа объясняет Docker через небольшой backend-проект. Внутри виртуальной машины Ubuntu Server 24.04 LTS запускаются три контейнера: Flask-приложение, PostgreSQL и pgAdmin.
Рабочая среда:
- Основная машина: Windows, Linux или macOS.
- Платформа виртуализации: Oracle VirtualBox.
- Гостевая операционная система: Ubuntu Server 24.04 LTS.
- Приложение: Flask-приложение на Python.
- База данных: PostgreSQL.
- Web-интерфейс базы данных: pgAdmin.
- Инструменты контейнеризации: Docker Engine и Docker Compose plugin.
- 1. Цель лабораторной работы
- 2. Что будет создано
- 3. Простая теория: зачем нужен Docker
- 4. Основные понятия Docker
- 5. Docker container vs virtual machine
- 6. Docker Compose vs Kubernetes
- 7. Docker Hub
- 8. Итоговая структура проекта
- 9. Настройка сети в Oracle VirtualBox
- 10. Установка Docker Engine и Docker Compose plugin
- 11. Создание папки проекта
- 12. Полный код всех файлов проекта
- 16. Сборка и запуск проекта
- 17. Проверка Flask
- 21. Ежедневные Docker-команды
- 24. Упражнение по Docker Hub
- 25. Типовые ошибки и исправления
- 30. Краткая памятка команд
Если Docker уже установлен, а все файлы проекта созданы, минимальный запуск выглядит так:
cp .env.example .env
docker compose config
docker compose up -d --build
curl http://localhost:8000/health
curl http://localhost:8000/visitsОжидаемая идея результата: Flask отвечает на HTTP-запросы, PostgreSQL принимает подключения, а endpoint /visits записывает данные в таблицу visits.
После выполнения этой работы вы сможете:
- Подготовить Ubuntu Server 24.04 LTS в Oracle VirtualBox для работы с Docker.
- Установить Docker Engine и Docker Compose plugin официальным способом.
- Создать простое Flask-приложение.
- Собрать Docker image на основе Dockerfile.
- Запустить несколько контейнеров через
docker compose. - Подключить Flask-контейнер к PostgreSQL-контейнеру через внутреннюю Docker-сеть.
- Сохранить данные PostgreSQL с помощью Docker volume.
- Проверить приложение через
curl, браузер, PostgreSQL CLI и pgAdmin. - Использовать ежедневные Docker-команды для диагностики и управления контейнерами.
- Понять связь между Docker, Docker Compose, Docker Hub и Kubernetes.
Главная идея: Docker помогает запускать приложение одинаково на разных машинах. Вы получаете управляемую среду, где Python, зависимости, база данных и web-интерфейс запускаются предсказуемо и очищаются безопасно.
В проекте будут работать 3 сервиса.
| Сервис | Имя контейнера | Назначение | Внешний порт на Ubuntu VM |
|---|---|---|---|
web |
flask_web |
Flask-приложение на Python | 8000 |
db |
postgres_db |
PostgreSQL database | 5432 |
pgadmin |
pgadmin_web |
Web-интерфейс для управления PostgreSQL | 5050 |
После запуска доступны следующие адреса:
http://localhost:8000
http://localhost:8000/health
http://localhost:8000/visits
http://localhost:8000/visits/count
http://localhost:8000/info
Если доступ выполняется с основной машины через Host-Only adapter, вместо localhost нужно использовать Host-Only IP Ubuntu Server:
http://UBUNTU_HOST_ONLY_IP:8000
http://UBUNTU_HOST_ONLY_IP:5050
Например:
http://192.168.56.101:8000
http://192.168.56.101:5050
flowchart TD
A[Ubuntu Server 24.04 LTS<br/>Oracle VirtualBox] --> B[Установка Docker Engine]
B --> C[Проверка docker и docker compose]
C --> D[Создание файлов проекта]
D --> E[Dockerfile<br/>сборка Flask image]
D --> F[docker-compose.yml<br/>web + db + pgAdmin]
E --> G[docker compose up -d --build]
F --> G
G --> H[Проверка Flask через curl/browser]
H --> I[Запись и чтение visits в PostgreSQL]
I --> J[Проверка данных через psql и pgAdmin]
J --> K[Понимание network, volume, logs, exec]
K --> L[Docker Hub: tag, login, push, pull]
L --> M[Kubernetes как следующий уровень]
classDef host fill:#eef2ff,stroke:#4338ca,stroke-width:2px,color:#111827;
classDef docker fill:#ecfeff,stroke:#0891b2,stroke-width:2px,color:#111827;
classDef app fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#111827;
classDef check fill:#fff7ed,stroke:#ea580c,stroke-width:2px,color:#111827;
class A host;
class B,C,D,E,F,G docker;
class H,I,J app;
class K,L,M check;
flowchart LR
subgraph HOST[Основная машина]
Browser[Browser или curl]
end
subgraph VM[Ubuntu Server 24.04 LTS<br/>Oracle VirtualBox]
Docker[Docker Engine]
subgraph Compose[Docker Compose project]
subgraph Net[lab_network bridge]
Web[web<br/>Flask app<br/>container: flask_web<br/>port: 8000]
DB[db<br/>PostgreSQL<br/>container: postgres_db<br/>port: 5432]
PGA[pgadmin<br/>pgAdmin<br/>container: pgadmin_web<br/>port: 80]
end
Vol[(postgres_data volume<br/>/var/lib/postgresql/data)]
end
end
Browser -->|HTTP :8000| Web
Browser -->|HTTP :5050| PGA
Docker --> Compose
Web -->|DB_HOST=db<br/>TCP 5432| DB
PGA -->|Host=db<br/>TCP 5432| DB
DB --> Vol
classDef outside fill:#f8fafc,stroke:#64748b,stroke-width:2px,color:#0f172a;
classDef vm fill:#eef2ff,stroke:#4f46e5,stroke-width:2px,color:#0f172a;
classDef net fill:#ecfeff,stroke:#06b6d4,stroke-width:2px,color:#0f172a;
classDef service fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#052e16;
classDef data fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#451a03;
class Browser outside;
class VM,Docker vm;
class Compose,Net net;
class Web,DB,PGA service;
class Vol data;
Без Docker учебные и реальные проекты часто сталкиваются с проблемами:
- на одной машине Python 3.10, на другой Python 3.12;
- у одного установлены нужные библиотеки, у другого нет;
- PostgreSQL установлен с другой версией или другой настройкой;
- приложение работает на одной машине, но не работает на другой;
- после экспериментов трудно полностью очистить систему.
Docker решает эти проблемы через контейнеры. Контейнер содержит приложение и его зависимости. Если image собран правильно, его можно запускать одинаково на разных машинах, где установлен Docker.
В этой лабораторной работе Docker особенно полезен потому, что не нужно устанавливать PostgreSQL и pgAdmin напрямую в Ubuntu. Они запускаются как отдельные контейнеры. Это чище, проще для удаления и ближе к реальным DevOps-практикам.
Docker image — это шаблон для создания контейнера. Его можно сравнить с готовым пакетом, где уже описано, какие файлы и зависимости нужны приложению.
В этой работе image для Flask содержит:
- Python 3.11;
- библиотеки из
requirements.txt; - файл
app.py; - команду запуска приложения.
Команда для просмотра локальных images:
docker imagesDocker container — это запущенный экземпляр image. Если image — это рецепт или шаблон, то container — это уже работающий процесс.
Команды:
docker psПоказывает только запущенные контейнеры.
docker ps -aПоказывает все контейнеры, включая остановленные.
Dockerfile — это инструкция сборки image. В нём записано:
- какой базовый image использовать;
- какую рабочую папку создать;
- какие файлы скопировать;
- какие зависимости установить;
- какую команду выполнить при запуске контейнера.
В этой работе Dockerfile строит image для Flask-приложения.
Docker Compose нужен, когда приложение состоит из нескольких связанных сервисов. Например, Flask отдельно, PostgreSQL отдельно, pgAdmin отдельно.
Без Compose пришлось бы вручную писать несколько длинных команд docker run, создавать сеть, подключать volumes и передавать переменные окружения. С Compose всё описано в одном файле docker-compose.yml, а запуск выполняется одной командой:
docker compose up -d --buildDocker Compose создаёт внутреннюю сеть. Внутри этой сети контейнеры могут обращаться друг к другу по имени сервиса.
В нашем проекте Flask подключается к PostgreSQL так:
DB_HOST=dbПочему db? Потому что в docker-compose.yml сервис PostgreSQL называется db.
Важно: внутри контейнера localhost означает сам этот контейнер. Если Flask-контейнер использует localhost, он будет искать PostgreSQL внутри себя, а PostgreSQL находится в другом контейнере. Поэтому правильно использовать db, а не localhost.
Дополнительная схема показывает главную сетевую идею Compose:
flowchart LR
Web[web<br/>Flask container] -->|правильно:<br/>DB_HOST=db| DB[db<br/>PostgreSQL container]
Pg[pgadmin<br/>pgAdmin container] -->|правильно:<br/>Host=db| DB
Web -. неправильно .-> Localhost[localhost внутри web<br/>это сам web-контейнер]
classDef ok fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#052e16;
classDef warn fill:#fee2e2,stroke:#dc2626,stroke-width:2px,color:#450a0a;
class Web,DB,Pg ok;
class Localhost warn;
Volume — это постоянное хранилище данных. Контейнер можно удалить и создать заново, но данные PostgreSQL должны сохраниться.
В этой работе используется volume:
volumes:
postgres_data:Он подключается к PostgreSQL:
- postgres_data:/var/lib/postgresql/dataВизуально volume работает так:
flowchart TD
A[postgres_db container] --> B["/var/lib/postgresql/data"]
B --> C[(postgres_data Docker volume)]
D[docker compose down] --> E[Контейнеры удалены<br>volume сохранён]
F[docker compose down -v] --> G[Контейнеры удалены<br>volume удалён<br>данные потеряны]
classDef data fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#451a03;
classDef safe fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#052e16;
classDef danger fill:#fee2e2,stroke:#dc2626,stroke-width:2px,color:#450a0a;
class C data;
class D,E safe;
class F,G danger;
Команда:
docker volume lsПоказывает volumes.
Важно:
docker compose downОстанавливает и удаляет контейнеры, но не удаляет volume с данными.
docker compose down -vОстанавливает контейнеры и удаляет volume. Это удаляет данные PostgreSQL.
Контейнер и виртуальная машина решают похожую задачу изоляции, но делают это по-разному.
flowchart TB
subgraph VMSTYLE[Virtual Machine]
HW1[Hardware]
HOSTOS1[Host OS]
HYP[Hypervisor / VirtualBox]
GOS1[Guest OS 1]
APP1[App + Libraries]
GOS2[Guest OS 2]
APP2[App + Libraries]
HW1 --> HOSTOS1 --> HYP
HYP --> GOS1 --> APP1
HYP --> GOS2 --> APP2
end
subgraph CONTSTYLE[Docker Container]
HW2[Hardware]
HOSTOS2[Host OS Linux kernel]
DOCKER[Docker Engine]
C1[Container 1<br/>App + Libraries]
C2[Container 2<br/>App + Libraries]
HW2 --> HOSTOS2 --> DOCKER
DOCKER --> C1
DOCKER --> C2
end
classDef vm fill:#eef2ff,stroke:#4f46e5,stroke-width:2px,color:#0f172a;
classDef docker fill:#ecfeff,stroke:#0891b2,stroke-width:2px,color:#0f172a;
class VMSTYLE,HW1,HOSTOS1,HYP,GOS1,GOS2,APP1,APP2 vm;
class CONTSTYLE,HW2,HOSTOS2,DOCKER,C1,C2 docker;
| Критерий | Docker container | Virtual machine |
|---|---|---|
| Уровень изоляции | Изоляция процессов | Полная гостевая операционная система |
| Kernel | Использует kernel host-системы | Имеет собственный guest OS kernel |
| Размер | Обычно меньше | Обычно больше |
| Запуск | Обычно быстрее | Обычно медленнее |
| Подходит для | Упаковка и запуск приложений | Запуск другой ОС или полной изолированной среды |
| Пример в этой работе | Flask, PostgreSQL, pgAdmin | Ubuntu Server внутри Oracle VirtualBox |
Контейнеры не заменяют виртуальные машины полностью. В этой лабораторной работе они используются вместе: Ubuntu Server работает как виртуальная машина, а внутри неё Docker запускает контейнеры.
Такой подход удобен для обучения, потому что можно экспериментировать внутри VM и не загрязнять основную систему.
Docker Compose и Kubernetes связаны с контейнерами, но предназначены для разных уровней сложности.
| Вопрос | Docker Compose | Kubernetes |
|---|---|---|
| Главная цель | Запуск нескольких контейнеров на одной машине | Управление контейнерами в кластере |
| Где удобно использовать | Локальная разработка, лабораторные работы, простые серверы | Production-системы, масштабирование, отказоустойчивость |
| Основной файл | docker-compose.yml |
Несколько YAML-файлов: Deployment, Service, ConfigMap, Secret и др. |
| Масштабирование | Простое и ручное | Более развитое и автоматизированное |
| Самовосстановление | Ограниченное | Встроенные механизмы rescheduling, health checks, rollout |
| Сложность | Ниже | Выше |
Рекомендуемый порядок изучения:
- Docker basics.
- Dockerfile.
- Docker Compose.
- Docker Hub.
- Kubernetes.
Kubernetes легче понять после уверенного понимания image, container, port, volume, network и service name.
Визуально разница выглядит так:
flowchart LR
subgraph ComposeSide[Docker Compose]
C1[Одна машина]
C2[docker-compose.yml]
C3[web + db + pgadmin]
C4[Локальная разработка<br/>и учебные проекты]
C1 --> C2 --> C3 --> C4
end
subgraph KubernetesSide[Kubernetes]
K1[Кластер машин]
K2[Deployment / Service / ConfigMap / Secret]
K3[Много экземпляров приложений]
K4[Production, scaling,<br/>self-healing, rollout]
K1 --> K2 --> K3 --> K4
end
C4 -. следующий уровень после уверенного Docker .-> K1
classDef compose fill:#ecfeff,stroke:#0891b2,stroke-width:2px,color:#0f172a;
classDef k8s fill:#eef2ff,stroke:#4f46e5,stroke-width:2px,color:#0f172a;
class ComposeSide,C1,C2,C3,C4 compose;
class KubernetesSide,K1,K2,K3,K4 k8s;
Docker Hub — это registry, то есть сервис для хранения и распространения Docker images.
Простая аналогия:
- GitHub хранит source code.
- Docker Hub хранит Docker images.
Типичный workflow:
- Написать приложение.
- Создать Dockerfile.
- Собрать image.
- Протестировать container локально.
- Сделать tag image.
- Выполнить
docker pushв Docker Hub. - На другой машине выполнить
docker pullи запустить image.
Важно: Docker Hub хранит images, но не хранит ваши локальные контейнеры и не хранит данные вашего PostgreSQL volume.
sequenceDiagram
participant Dev as Ubuntu VM
participant Local as Local Docker image
participant Hub as Docker Hub registry
participant Other as Другая машина
Dev->>Local: docker compose build web
Dev->>Local: docker tag image username/repository:1.0
Dev->>Hub: docker login
Dev->>Hub: docker push username/repository:1.0
Other->>Hub: docker pull username/repository:1.0
Other->>Other: docker run или docker compose up
Создайте такую структуру:
docker-flask-postgres-lab/
├── app.py
├── requirements.txt
├── Dockerfile
├── docker-compose.yml
├── .env.example
├── .gitignore
├── .dockerignore
└── README.md
Файл .env создаётся локально из .env.example и не публикуется в GitHub.
В этой публикационной версии все основные объяснения, шаги, команды и код находятся в README.md. Репозиторий остаётся компактным и удобным для чтения на GitHub.
Для лабораторной работы удобно использовать 2 сетевых адаптера.
| Adapter | Mode | Назначение |
|---|---|---|
| Adapter 1 | NAT | Интернет внутри Ubuntu Server |
| Adapter 2 | Host-Only Adapter | Доступ к Ubuntu Server с основной машины |
В Oracle VirtualBox Manager откройте:
Tools → Network → Host-only Networks
Рекомендуемые параметры:
IPv4 Address: 192.168.56.1
IPv4 Network Mask: 255.255.255.0
DHCP Server: Enabled
Lower Address Bound: 192.168.56.101
Upper Address Bound: 192.168.56.254
Выключите Ubuntu Server VM и откройте:
Ubuntu VM → Settings → Network
Настройте:
Adapter 1: NAT
Adapter 2: Host-only Adapter
В Ubuntu выполните:
ip -br addrПример результата:
enp0s3 UP 10.0.2.15/24
enp0s8 UP 192.168.56.101/24
Обычно:
10.0.2.15— NAT-интерфейс;192.168.56.101— Host-Only интерфейс.
Запомните Host-Only IP. Далее он называется:
UBUNTU_HOST_ONLY_IP
Проверьте интерфейсы:
ip -br linkУстановите DHCP client, если он отсутствует:
sudo apt-get update
sudo apt-get install -y isc-dhcp-clientПоднимите интерфейс. Если ваш Host-Only интерфейс называется не enp0s8, замените имя:
sudo ip link set enp0s8 up
sudo dhclient enp0s8
ip -br addrЭти команды основаны на официальной инструкции Docker для Ubuntu.
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
sudo apt-get remove -y "$pkg" || true
donesudo apt-get update
sudo apt-get install -y ca-certificates curlsudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.ascsudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOFsudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugindocker --version
docker compose version
sudo docker run hello-world
sudo systemctl status docker --no-pagerЕсли hello-world запустился, Docker работает.
Для учебной VM можно добавить пользователя в группу docker:
sudo groupadd docker 2>/dev/null || true
sudo usermod -aG docker $USER
newgrp docker
docker run hello-worldВажное замечание по безопасности: пользователь в группе docker получает очень широкие права на машине. Для учебной VM это удобно, но на production-сервере такое решение нужно применять осторожно.
Выполните:
cd ~
mkdir docker-flask-postgres-lab
cd docker-flask-postgres-labСоздайте файлы:
touch app.py requirements.txt Dockerfile docker-compose.yml .env.example .gitignore .dockerignore README.mdПроверьте:
ls -laВ этом разделе находится весь код, который нужен для запуска лабораторной работы. Скопируйте каждый блок в соответствующий файл.
Назначение: Flask-приложение. Оно подключается к PostgreSQL, создаёт таблицу visits, добавляет визиты и показывает служебные endpoints.
import os
import time
from datetime import datetime, timezone
import psycopg2
from flask import Flask, jsonify, request
from psycopg2.extras import RealDictCursor
app = Flask(__name__)
APP_VERSION = os.getenv("APP_VERSION", "1.0.0")
def get_db_connection():
"""Создаёт новое подключение к PostgreSQL, используя переменные окружения."""
return psycopg2.connect(
host=os.getenv("DB_HOST", "db"),
port=os.getenv("DB_PORT", "5432"),
database=os.getenv("DB_NAME", "labdb"),
user=os.getenv("DB_USER", "labuser"),
password=os.getenv("DB_PASSWORD", "labpassword"),
connect_timeout=3,
)
def wait_for_database(max_attempts=30, delay_seconds=2):
"""Ждёт, пока PostgreSQL станет доступен для подключения."""
for attempt in range(1, max_attempts + 1):
try:
with get_db_connection() as connection:
with connection.cursor() as cursor:
cursor.execute("SELECT 1;")
print("Database is ready.", flush=True)
return
except Exception as error:
print(
f"Waiting for database... attempt {attempt}/{max_attempts}. Error: {error}",
flush=True,
)
time.sleep(delay_seconds)
raise RuntimeError("Database connection failed after waiting.")
def initialize_database():
"""Создаёт таблицу visits, если она ещё не существует."""
with get_db_connection() as connection:
with connection.cursor() as cursor:
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS visits (
id SERIAL PRIMARY KEY,
client_ip TEXT,
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
"""
)
connection.commit()
@app.get("/")
def home():
return """
<h1>Docker Flask PostgreSQL Lab</h1>
<p>Flask-приложение работает внутри Docker-контейнера.</p>
<p>PostgreSQL работает в отдельном контейнере, а контейнеры общаются через Docker Compose network.</p>
<ul>
<li><a href="/health">/health</a> — проверка Flask и PostgreSQL</li>
<li><a href="/visits">/visits</a> — добавить визит и показать последние записи</li>
<li><a href="/visits/count">/visits/count</a> — общее количество визитов</li>
<li><a href="/info">/info</a> — техническая информация о запуске</li>
</ul>
"""
@app.get("/health")
def health():
try:
with get_db_connection() as connection:
with connection.cursor() as cursor:
cursor.execute("SELECT 1;")
database_status = "connected"
status_code = 200
except Exception as error:
database_status = f"not connected: {error}"
status_code = 503
return (
jsonify(
{
"status": "ok" if status_code == 200 else "error",
"database": database_status,
"app_version": APP_VERSION,
"checked_at_utc": datetime.now(timezone.utc).isoformat(),
}
),
status_code,
)
@app.get("/visits")
def visits():
client_ip = request.headers.get("X-Forwarded-For", request.remote_addr)
user_agent = request.headers.get("User-Agent", "unknown")
with get_db_connection() as connection:
with connection.cursor(cursor_factory=RealDictCursor) as cursor:
cursor.execute(
"""
INSERT INTO visits (client_ip, user_agent)
VALUES (%s, %s)
RETURNING id, created_at;
""",
(client_ip, user_agent),
)
new_visit = cursor.fetchone()
cursor.execute(
"""
SELECT id, client_ip, user_agent, created_at
FROM visits
ORDER BY id DESC
LIMIT 10;
"""
)
latest_visits = cursor.fetchall()
connection.commit()
return jsonify(
{
"status": "success",
"new_visit": dict(new_visit),
"latest_visits": [dict(row) for row in latest_visits],
}
)
@app.get("/visits/count")
def visits_count():
with get_db_connection() as connection:
with connection.cursor() as cursor:
cursor.execute("SELECT COUNT(*) FROM visits;")
total = cursor.fetchone()[0]
return jsonify({"total_visits": total})
@app.get("/info")
def info():
return jsonify(
{
"app_version": APP_VERSION,
"db_host_inside_compose_network": os.getenv("DB_HOST", "db"),
"db_name": os.getenv("DB_NAME", "labdb"),
"message": "В Docker Compose контейнеры обращаются друг к другу по имени сервиса, например db, а не localhost.",
}
)
if __name__ == "__main__":
wait_for_database()
initialize_database()
app.run(host="0.0.0.0", port=8000)Пояснение важных строк:
os.getenv(...)читает настройки из переменных окружения.DB_HOSTпо умолчанию равенdb, потому что это имя сервиса PostgreSQL в Docker Compose.wait_for_database()ждёт готовности PostgreSQL, чтобы Flask не завершился слишком рано.initialize_database()создаёт таблицуvisits, если её ещё нет.app.run(host="0.0.0.0", port=8000)разрешает доступ к Flask внутри контейнерной сети и через опубликованный порт.
Назначение: список Python-зависимостей.
flask==3.*
psycopg2-binary==2.9.*
Пояснение:
flaskнужен для web-приложения.psycopg2-binaryнужен для подключения Python к PostgreSQL.
Назначение: инструкция для сборки image Flask-приложения.
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PIP_NO_CACHE_DIR=1
WORKDIR /app
RUN useradd --create-home --shell /bin/bash appuser
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
CMD ["python", "app.py"]Пояснение по шагам:
FROM python:3.11-slimберёт официальный Python image.WORKDIR /appзадаёт рабочую папку внутри container.COPY requirements.txt .копирует файл зависимостей.RUN pip install ...устанавливает зависимости.COPY app.py .копирует приложение.USER appuserзапускает приложение не от root, что является более аккуратной практикой.EXPOSE 8000документирует порт приложения внутри container.CMD ["python", "app.py"]запускает приложение.
Назначение: описание всех сервисов лабораторной работы.
name: docker-flask-postgres-lab
services:
web:
build:
context: .
dockerfile: Dockerfile
image: docker-flask-postgres-lab-web:1.0
container_name: flask_web
env_file:
- .env
environment:
APP_VERSION: ${APP_VERSION:-1.0.0}
ports:
- "${WEB_PORT:-8000}:8000"
depends_on:
db:
condition: service_healthy
networks:
- lab_network
restart: unless-stopped
db:
image: postgres:16
container_name: postgres_db
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- "${POSTGRES_HOST_PORT:-5432}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
networks:
- lab_network
restart: unless-stopped
pgadmin:
image: dpage/pgadmin4:latest
container_name: pgadmin_web
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
ports:
- "${PGADMIN_HOST_PORT:-5050}:80"
depends_on:
db:
condition: service_healthy
networks:
- lab_network
restart: unless-stopped
volumes:
postgres_data:
networks:
lab_network:
driver: bridgeПояснение ключевых частей:
servicesсодержит 3 сервиса:web,db,pgadmin.webсобирается из локальногоDockerfile.dbиспользует готовый imagepostgres:16.pgadminиспользует готовый imagedpage/pgadmin4:latest.depends_onсcondition: service_healthyпомогает дождаться готовности PostgreSQL.portsпубликует порты контейнеров на Ubuntu VM.volumesсохраняет данные PostgreSQL.networksсоздаёт отдельную bridge-сеть для контейнеров.
Назначение: шаблон переменных окружения. Его можно хранить в GitHub.
# Flask application
APP_VERSION=1.0.0
WEB_PORT=8000
# PostgreSQL container settings
POSTGRES_DB=labdb
POSTGRES_USER=labuser
POSTGRES_PASSWORD=change_me_lab_password
POSTGRES_HOST_PORT=5432
# Flask-to-PostgreSQL connection settings inside the Docker Compose network
# Important: DB_HOST must be the Compose service name "db", not localhost.
DB_HOST=db
DB_PORT=5432
DB_NAME=labdb
DB_USER=labuser
DB_PASSWORD=change_me_lab_password
# pgAdmin web interface
PGADMIN_DEFAULT_EMAIL=[email protected]
PGADMIN_DEFAULT_PASSWORD=change_me_pgadmin_password
PGADMIN_HOST_PORT=5050После копирования создаётся настоящий .env:
cp .env.example .env
nano .envВажно: .env содержит пароли и локальные настройки, поэтому его нельзя публиковать в GitHub.
Назначение: не отправлять временные и секретные файлы в Git.
.env
__pycache__/
*.py[cod]
.venv/
venv/
.env.*
!.env.example
.DS_Store
.vscode/
.idea/Назначение: не отправлять лишние файлы в Docker build context.
.git
.gitignore
.env
__pycache__/
*.py[cod]
.venv/
venv/
README.md
docs/
images/
.DS_Store
Если удобнее создать файлы прямо из терминала, можно использовать cat <<'EOF'.
Пример для requirements.txt:
cat > requirements.txt <<'EOF'
flask==3.*
psycopg2-binary==2.9.*
EOFПример для .env.example:
cat > .env.example <<'EOF'
# Flask application
APP_VERSION=1.0.0
WEB_PORT=8000
# PostgreSQL container settings
POSTGRES_DB=labdb
POSTGRES_USER=labuser
POSTGRES_PASSWORD=change_me_lab_password
POSTGRES_HOST_PORT=5432
# Flask-to-PostgreSQL connection settings inside the Docker Compose network
# Important: DB_HOST must be the Compose service name "db", not localhost.
DB_HOST=db
DB_PORT=5432
DB_NAME=labdb
DB_USER=labuser
DB_PASSWORD=change_me_lab_password
# pgAdmin web interface
[email protected]
PGADMIN_DEFAULT_PASSWORD=change_me_pgadmin_password
PGADMIN_HOST_PORT=5050
EOFДля больших файлов, таких как app.py и docker-compose.yml, обычно удобнее использовать nano:
nano app.py
nano docker-compose.ymlСохранение в nano:
Ctrl + O → Enter → Ctrl + X
Создайте реальный файл окружения:
cp .env.example .env
nano .envМинимальный корректный вариант:
APP_VERSION=1.0.0
WEB_PORT=8000
POSTGRES_DB=labdb
POSTGRES_USER=labuser
POSTGRES_PASSWORD=change_me_lab_password
POSTGRES_HOST_PORT=5432
DB_HOST=db
DB_PORT=5432
DB_NAME=labdb
DB_USER=labuser
DB_PASSWORD=change_me_lab_password
PGADMIN_DEFAULT_EMAIL=[email protected]
PGADMIN_DEFAULT_PASSWORD=change_me_pgadmin_password
PGADMIN_HOST_PORT=5050Практическая подсказка: POSTGRES_PASSWORD и DB_PASSWORD должны совпадать, потому что первый пароль создаёт пользователя PostgreSQL, а второй используется Flask-приложением для подключения.
Перед запуском всегда проверяйте файлы.
ls -laДолжны быть видны:
app.py
requirements.txt
Dockerfile
docker-compose.yml
.env.example
.env
.gitignore
.dockerignore
README.md
Если Python установлен на Ubuntu:
python3 -m py_compile app.pyЕсли команда ничего не вывела, синтаксис корректный.
docker compose configЭта команда полезна, потому что показывает итоговую конфигурацию после подстановки переменных из .env.
Если есть YAML-ошибка, Docker Compose сообщит строку и причину.
Запуск:
docker compose up -d --buildПояснение:
docker composeзапускает Compose plugin.upсоздаёт и запускает сервисы.-dзапускает контейнеры в фоновом режиме.--buildпересобирает Flask image перед запуском.
Проверьте контейнеры:
docker compose ps
docker psОжидаемый результат:
flask_web
postgres_db
pgadmin_web
Смотрите logs:
docker compose logsЖивой просмотр logs:
docker compose logs -fОстановить просмотр logs:
Ctrl + C
sequenceDiagram
participant User as Terminal
participant Compose as Docker Compose
participant DB as db / PostgreSQL
participant Web as web / Flask
participant Pg as pgAdmin
User->>Compose: docker compose up -d --build
Compose->>Compose: read docker-compose.yml and .env
Compose->>Web: build Flask image from Dockerfile
Compose->>DB: create postgres_db
DB-->>Compose: healthcheck pg_isready = healthy
Compose->>Web: start flask_web after db is healthy
Web->>DB: connect using DB_HOST=db
Web->>DB: create table visits if not exists
Compose->>Pg: start pgadmin_web after db is healthy
User->>Web: curl http://localhost:8000/health
Web-->>User: JSON status ok
Выполните:
curl http://localhost:8000/
curl http://localhost:8000/health
curl http://localhost:8000/visits
curl http://localhost:8000/visits/count
curl http://localhost:8000/infoОжидаемый смысл ответа /health:
{
"status": "ok",
"database": "connected"
}Если /visits вызывается несколько раз, количество записей должно увеличиваться.
Проверьте это так:
curl http://localhost:8000/visits
curl http://localhost:8000/visits
curl http://localhost:8000/visits/countsequenceDiagram
participant Browser as Browser / curl
participant Flask as Flask container web
participant DB as PostgreSQL container db
participant Volume as postgres_data volume
Browser->>Flask: GET /visits
Flask->>DB: INSERT INTO visits
DB->>Volume: сохранить строку на disk volume
DB-->>Flask: new id and created_at
Flask->>DB: SELECT latest 10 visits
DB-->>Flask: rows
Flask-->>Browser: JSON response
Сначала найдите Host-Only IP:
ip -br addrЗатем с основной машины откройте:
http://UBUNTU_HOST_ONLY_IP:8000
http://UBUNTU_HOST_ONLY_IP:8000/health
http://UBUNTU_HOST_ONLY_IP:8000/visits
http://UBUNTU_HOST_ONLY_IP:5050
Пример:
http://192.168.56.101:8000
http://192.168.56.101:5050
Если с Ubuntu curl localhost:8000 работает, а с основной машины браузер не открывает страницу, проверьте:
- Host-Only IP.
- VirtualBox Adapter 2.
- Firewall UFW.
- Публикацию ports в
docker-compose.yml.
Если UFW включён:
sudo ufw status
sudo ufw allow 8000/tcp
sudo ufw allow 5050/tcp
sudo ufw reloadВойти в PostgreSQL CLI внутри контейнера:
docker exec -it postgres_db psql -U labuser -d labdbВнутри psql выполните:
\dt
SELECT * FROM visits;
SELECT COUNT(*) FROM visits;
\qМожно выполнить SQL одной командой без входа в интерактивный режим:
docker exec -it postgres_db psql -U labuser -d labdb -c "SELECT COUNT(*) FROM visits;"Пояснение:
docker execвыполняет команду внутри уже работающего контейнера.-itделает команду интерактивной.postgres_db— имя контейнера.psql— PostgreSQL command-line client.
Откройте в браузере:
http://UBUNTU_HOST_ONLY_IP:5050
Войдите:
Email: [email protected]
Password: значение PGADMIN_DEFAULT_PASSWORD из .env
Добавьте server:
Name: Docker PostgreSQL Lab
Host name/address: db
Port: 5432
Maintenance database: labdb
Username: labuser
Password: значение DB_PASSWORD из .env
Важно: pgAdmin тоже работает в контейнере. Поэтому для подключения к PostgreSQL он использует db, а не localhost.
Эти команды важно понимать и регулярно использовать.
docker psПоказывает работающие контейнеры.
docker ps -aПоказывает все контейнеры.
docker compose psПоказывает контейнеры текущего Compose-проекта.
docker images
docker volume ls
docker network lsВсе logs проекта:
docker compose logsLogs в реальном времени:
docker compose logs -fLogs только Flask:
docker compose logs webLogs только PostgreSQL:
docker compose logs dbВойти в Flask container:
docker exec -it flask_web shПосмотреть файлы внутри:
ls -la
pwd
exitВойти в PostgreSQL CLI:
docker exec -it postgres_db psql -U labuser -d labdbОстановить проект:
docker compose downОстановить проект и удалить database volume:
docker compose down -vЗапустить снова:
docker compose up -dЕсли изменили app.py:
docker compose build web
docker compose up -dИли одной командой:
docker compose up -d --buildПересборка без cache:
docker compose build --no-cache webПосмотреть использование диска:
docker system dfУдалить неиспользуемые объекты:
docker system pruneБолее агрессивная очистка, включая unused images:
docker system prune -aПредупреждение: не используйте очистку без понимания, если на машине есть другие проекты.
Проверить IP:
ip -br addrПроверить открытые ports:
sudo ss -tulpenПроверить конкретный port:
sudo ss -tulpen | grep ':8000'
sudo ss -tulpen | grep ':5050'
sudo ss -tulpen | grep ':5432'Проверить firewall:
sudo ufw statusРазрешить ports:
sudo ufw allow 8000/tcp
sudo ufw allow 5050/tcpРедактировать файл:
nano docker-compose.ymlПоказать последние команды:
history | tail -30Полный поток такой:
Dockerfile → docker image → docker container
В этой лабораторной работе:
Dockerfile builds Flask image
Flask image runs as flask_web container
PostgreSQL image runs as postgres_db container
pgAdmin image runs as pgadmin_web container
Docker Compose connects them together
Логика подключения:
Browser or curl → Ubuntu VM port 8000 → Flask container port 8000
Flask container → Docker network → db:5432 → PostgreSQL container
pgAdmin browser → Ubuntu VM port 5050 → pgAdmin container → db:5432
Главный научно-технический смысл: контейнеризация отделяет приложение от конкретной ручной настройки операционной системы. Это повышает воспроизводимость, упрощает переносимость и помогает управлять зависимостями.
Этот раздел необязательный, но очень полезный.
docker loginВведите Docker Hub username и password/token.
docker compose build webПроверьте image:
docker imagesЗамените YOUR_DOCKERHUB_USERNAME на свой username:
docker tag docker-flask-postgres-lab-web:1.0 YOUR_DOCKERHUB_USERNAME/docker-flask-postgres-lab-web:1.0docker push YOUR_DOCKERHUB_USERNAME/docker-flask-postgres-lab-web:1.0docker pull YOUR_DOCKERHUB_USERNAME/docker-flask-postgres-lab-web:1.0Важное понимание: Docker Hub позволяет распространять image. Но для полного запуска приложения всё равно нужны настройки окружения, база данных и Compose-файл или аналогичная инфраструктура.
Пример проблемы: порт 8000, 5050 или 5432 уже занят.
Проверка:
sudo ss -tulpen | grep ':8000'
sudo ss -tulpen | grep ':5050'
sudo ss -tulpen | grep ':5432'Исправление: изменить ports в .env:
WEB_PORT=8001
PGADMIN_HOST_PORT=5051
POSTGRES_HOST_PORT=5433Перезапуск:
docker compose down
docker compose up -d --buildПроверьте logs:
docker compose logs web
docker compose logs dbПроверьте .env:
DB_HOST=db
DB_PORT=5432
DB_NAME=labdb
DB_USER=labuser
DB_PASSWORD=change_me_lab_passwordЧастая ошибка: используется DB_HOST=localhost. Это неправильно внутри Compose network.
В pgAdmin server connection используйте:
Host name/address: db
Port: 5432
Не используйте localhost, потому что pgAdmin работает в отдельном контейнере.
Исправление:
sudo usermod -aG docker $USER
newgrp docker
docker run hello-worldЕсли не помогло, перезапустите Ubuntu VM.
docker compose down -v
docker compose up -d --buildПредупреждение: -v удаляет volume и все данные PostgreSQL.
Пересоберите image:
docker compose up -d --buildЕсли браузер показывает старую страницу:
Ctrl + F5
Диагностику удобно вести по дереву:
flowchart TD
A[Проблема] --> B{{Что именно не работает?}}
B -->|docker command not found| C[Docker не установлен<br/>или shell не обновлён]
C --> C1[docker --version<br/>newgrp docker<br/>logout/login]
B -->|permission denied| D[Пользователь не в docker group]
D --> D1[sudo usermod -aG docker $USER<br/>newgrp docker]
B -->|port already allocated| E[Порт уже занят]
E --> E1[ss -tulpn<br/>изменить WEB_PORT или PGADMIN_HOST_PORT в .env]
B -->|database not connected| F[Ошибка DB_HOST<br/>или PostgreSQL ещё не готов]
F --> F1[DB_HOST=db<br/>docker compose logs db<br/>docker compose ps]
B -->|pgAdmin cannot connect| G[Неверный host в pgAdmin]
G --> G1[Host name/address = db<br/>Username = labuser]
B -->|data disappeared| H[Volume был удалён]
H --> H1[docker volume ls<br/>не использовать down -v без необходимости]
classDef problem fill:#fee2e2,stroke:#dc2626,stroke-width:2px,color:#450a0a;
classDef decision fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#451a03;
classDef fix fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#052e16;
class A problem;
class B decision;
class C,D,E,F,G,H,C1,D1,E1,F1,G1,H1 fix;
Если что-то не работает, идите сверху вниз:
docker compose config
docker compose ps
docker compose logs web
docker compose logs db
curl http://localhost:8000/health
sudo ss -tulpen | grep ':8000'
ip -br addrИнтерпретация:
docker compose configпроверяет YAML и.env.docker compose psпоказывает состояние контейнеров.logs webпоказывает ошибки Flask.logs dbпоказывает ошибки PostgreSQL.curl /healthпроверяет приложение и подключение к базе данных.ssпроверяет, слушает ли Ubuntu нужный port.ip -br addrпомогает проверить Host-Only IP.
- Что такое Docker image?
- Что такое Docker container?
- Чем Dockerfile отличается от docker-compose.yml?
- Почему Flask использует
DB_HOST=db, а неlocalhost? - Что делает Docker volume?
- Чем отличается
docker compose downотdocker compose down -v? - Почему Docker удобен для командной разработки?
- Чем Docker container отличается от virtual machine?
- Для чего нужен Docker Hub?
- Почему Kubernetes обычно изучают после Docker Compose?
В .env измените:
APP_VERSION=1.0.1Перезапустите:
docker compose up -d
curl http://localhost:8000/infoВ .env измените:
WEB_PORT=8001Перезапустите:
docker compose down
docker compose up -d
curl http://localhost:8001/healthДобавьте visits:
curl http://localhost:8000/visits
curl http://localhost:8000/visits
curl http://localhost:8000/visits/countОстановите и запустите проект:
docker compose down
docker compose up -d
curl http://localhost:8000/visits/countЕсли count сохранился, volume работает.
docker compose down -v
docker compose up -d --build
curl http://localhost:8000/visits/countДанные должны начаться заново.
Добавьте в app.py:
@app.get("/hello/<name>")
def hello(name):
return jsonify({"message": f"Hello, {name}"})Пересоберите:
docker compose up -d --build
curl http://localhost:8000/hello/AliПодготовьте скриншоты или вывод терминала для:
docker --versiondocker compose versiondocker compose configdocker compose pscurl http://localhost:8000/healthcurl http://localhost:8000/visitscurl http://localhost:8000/visits/countdocker exec -it postgres_db psql -U labuser -d labdb -c "SELECT * FROM visits;"- pgAdmin page, где видна таблица
visits. - Короткие ответы на контрольные вопросы 4, 5 и 6.
# Docker version
docker --version
docker compose version
# Start lab
docker compose up -d --build
# Status
docker compose ps
docker ps
# Logs
docker compose logs
docker compose logs -f
docker compose logs web
docker compose logs db
# Test app
curl http://localhost:8000/health
curl http://localhost:8000/visits
curl http://localhost:8000/visits/count
# Enter containers
docker exec -it flask_web sh
docker exec -it postgres_db psql -U labuser -d labdb
# Stop
docker compose down
# Stop and delete database data
docker compose down -v
# Rebuild
docker compose up -d --build
docker compose build --no-cache web
# Docker objects
docker images
docker volume ls
docker network ls
docker system df
# Cleanup unused objects
docker system pruneПеред публикацией на GitHub проверьте, что в репозитории есть только безопасные и нужные файлы:
git status
ls -laДолжны публиковаться:
README.md
app.py
requirements.txt
Dockerfile
docker-compose.yml
.env.example
.gitignore
.dockerignore
Не должен публиковаться:
.env
Финальная техническая проверка перед commit:
python3 -m py_compile app.py
docker compose config
docker compose up -d --build
curl http://localhost:8000/health
curl http://localhost:8000/visits/count
docker compose downЕсли все команды выполняются без ошибок, лабораторная работа готова к публикации.
mindmap
root((Docker Lab))
Environment
VirtualBox
Ubuntu Server 24.04 LTS
Docker Engine
Docker Compose plugin
Application
Flask
Python dependencies
Dockerfile
Image
Container
Database
PostgreSQL
Volume
Persistent data
pgAdmin
Networking
Compose service names
DB_HOST=db
Published ports
Host-Only IP
Daily operations
docker compose ps
docker compose logs
docker exec
docker compose down
docker compose up -d --build
Registry
Docker Hub
tag
push
pull
Next step
Kubernetes
Cluster
Scaling
Self-healing
Docker не является полной виртуальной машиной. Он запускает изолированные процессы на основе images и использует возможности операционной системы для изоляции. Виртуальная машина запускает полноценную гостевую операционную систему. Поэтому контейнеры обычно легче и быстрее, но виртуальные машины дают более полную изоляцию на уровне операционной системы.
Docker Compose не заменяет Kubernetes. Compose удобен для обучения, локальной разработки и простых multi-container приложений. Kubernetes используется там, где нужны кластеры, масштабирование, самовосстановление, rolling updates, service discovery и более сложное управление production-системами.
Docker Hub не запускает приложение сам по себе. Он хранит и распространяет images. Чтобы приложение работало, image нужно запустить на машине или в инфраструктуре, где есть Docker, Compose, Kubernetes или другой container runtime/workload manager.
После этой лабораторной работы важно понимать не только команды, но и архитектуру:
Host machine → VirtualBox VM → Ubuntu Server → Docker Engine → Containers → Application services
И внутреннюю связь сервисов:
Flask container → Docker network → PostgreSQL container
pgAdmin container → Docker network → PostgreSQL container
Browser/curl → published port → Flask or pgAdmin container
Это и есть практическая польза Docker: reproducibility, isolation, portability, clean deployment and easier debugging.



