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

Dockerfile — 10 типовых образов


Для кого эта статья

Если в Google или Яндексе вы вводите dockerfile example, dockerfile nodejs, python dockerfile flask, golang dockerfile multistage, react nginx dockerfile, spring boot dockerfile, php dockerfile apache или как собрать docker образ — вы попали в галерею готовых Dockerfile с разбором каждой строки, как в популярной галерее Turtle на Python: сначала рабочий код, потом объяснение «зачем так», потом проверка.

Статья для:

  • школьников и студентов — лабораторная, курсовой, отчёт «приложение в Docker»;
  • самоучек, которые скопировали чужой Dockerfile и хотят понять, что означает COPY, WORKDIR, EXPOSE;
  • тех, кто уже нашёл Docker Compose — готовые стеки и теперь нужен build: . — свой образ под Node, Python, Go или фронт.
Сначала теория — потом копирование

Полный справочник инструкций — глава Dockerfile. Команды CLI — Docker. Несколько контейнеров — Compose. Nginx после сборки фронта — конфиги nginx.


Как читать каждый пример в галерее

У всех десяти карточек одинаковая структура — так проще искать нужный стек и сдавать лабораторную:

БлокЧто внутри
ЗадачаЧто вы получите и типичные запросы в поиске
Смысл простыми словамиЗачем этот Dockerfile, без жаргона
Структура папкиКакие файлы положить рядом
DockerfileГотовый текст для копирования
Разбор по строкамТаблица «строка → что делает Docker»
Что происходит при docker buildПошагово, какие слои создаются
Сборка и проверкаКоманды и что вы должны увидеть
Если не работаетТипичные ошибки новичков
Как в галерее Turtle

В примерах Turtle вы копируете код, запускаете и видите черепашку. Здесь то же самое: скопировали Dockerfile → docker builddocker run → открыли браузер или прочитали вывод в терминале. Разница только в том, что «холст» — это образ, а «кисть» — инструкции FROM, COPY, RUN.


Три слова — образ, контейнер, слой

ТерминПростыми словамиАналогия
DockerfileТекстовый рецепт «как собрать программу в коробку»Рецепт торта
Образ (image)Готовая «коробка» с ОС, файлами и командой запускаЗамороженный торт из магазина
КонтейнерЗапущенный экземпляр образа (процесс)Торт на столе, который едят
Слой (layer)Каждая строка RUN / COPY добавляет шаг; Docker кэширует неизменённые шагиСлои в торте: пока не меняете крем, нижние слои не перепекаете
Dockerfile docker build docker run
────────── ──────────── ──────────
FROM node... → образ my-api:1 → контейнер (процесс node)
COPY ...
RUN npm ci
CMD ["node", ...]

Контекст сборки — папка, которую вы указываете точкой в конце: docker build -t имя .
В эту папку Docker смотрит при COPY. Лишнее отсекает .dockerignore.

Загрузка интерактивного демо…

Поиграйте с порядком инструкций в симуляторе выше — затем разберите пример №3 Node.js: там видно, зачем сначала копируют package-lock.json, потом исходники.


Шпаргалка инструкций Dockerfile

ИнструкцияКогда выполняетсяЗачем нужна
FROMНачало сборки / новая стадия«На чём строим» — alpine, node, python…
WORKDIRПри сборке и в контейнереТекущая папка для COPY и RUN
COPYСборкаСкопировать файлы с вашего ПК в образ
RUNСборкаУстановить пакеты, собрать проект
ENVСборка + контейнерПеременные среды (NODE_ENV, пути)
EXPOSEДокументацияКакой порт слушает приложение внутри
USERСборка + контейнерОт какого пользователя идёт процесс
CMD / ENTRYPOINTТолько при docker runЧто запустить, когда контейнер стартует
HEALTHCHECKВо время работы контейнераDocker периодически проверяет «жив ли сервис»

Важно: EXPOSE 3000 не открывает порт на вашем ноутбуке. Чтобы зайти из браузера, нужен docker run -p 8080:3000 (слева — ваш ПК, справа — порт в контейнере).


Как пользоваться галереей

  1. Создайте папку проекта (например my-api/).
  2. Положите Dockerfile и .dockerignore в корень.
  3. Соберите образ (имя и тег — любые):
docker build -t myapp:1 .
  1. Запустите контейнер:
docker run --rm -p 8080:3000 myapp:1
Часть командыСмысл
docker buildПрочитать Dockerfile и собрать образ
-t myapp:1Имя образа myapp, тег 1 (версия)
.Контекст — текущая папка
docker runСоздать контейнер из образа и запустить
--rmУдалить контейнер после остановки
-p 8080:3000localhost:8080 на ПК → порт 3000 в контейнере
Docker Desktop на Windows

Перед docker build Docker Desktop должен быть Running. Ошибка Cannot connect to the Docker daemon — демон не запущен, а не опечатка в Dockerfile.


Общий .dockerignore

Создайте файл .dockerignore рядом с Dockerfile:

.git
.gitignore
.env
*.log
node_modules
__pycache__
.venv
dist
build
target
coverage
.idea
.vscode
README.md

Разбор по строкам:

СтрокаЗачем
.gitИстория Git не нужна в образе; сборка быстрее
.envПароли и ключи нельзя запекать в слои образа
node_modulesЗависимости ставят npm ci внутри RUN, а не копируют с Windows/macOS
dist / targetСобранные файлы с хоста могут быть от другой ОС
README.mdДокументация в runtime не нужна

Без .dockerignore команда COPY . . может затянуть гигабайты и сломать кэш.


Оглавление — 10 образов

СценарийПоиск в GoogleПорт
1Проверка Dockerdockerfile alpine hello world
2Статический сайтnginx dockerfile static80
3Node.js APInode dockerfile example production3000
4Python APIpython dockerfile flask gunicorn5000
5Go-сервисgolang dockerfile multistage8080
6React/Vue + nginxreact dockerfile nginx vite80
7Spring Bootspring boot dockerfile jar8080
8PHP-сайтphp dockerfile apache80
9.NET APIaspnet core dockerfile8080
10Миграции / seeddocker run once script

1. Минимальный образ — «Привет, Docker»

Задача: убедиться, что Docker установлен и команды build / run работают.
Поиск: dockerfile alpine hello world, docker tutorial first image.

Смысл простыми словами: вы не пишете приложение — вы проверяете цепочку «рецепт → образ → контейнер». Три строки Dockerfile достаточно для первой лабораторной по контейнерам.

Структура:

hello/
├── Dockerfile
└── .dockerignore

Dockerfile:

FROM alpine:3.19

RUN echo "Образ собран успешно"

CMD ["echo", "Привет из контейнера!"]

Разбор по строкам:

СтрокаЧто делает DockerЗачем так
FROM alpine:3.19Берёт готовый базовый образ с Docker Hub (~7 МБ)Минимальная «операционка» для учебы; тег 3.19 фиксирует версию
RUN echo "Образ собран…"Во время сборки выполняет команду и создаёт новый слойПоказывает разницу: RUN = при build, не при run
CMD ["echo", "Привет…"]Команда по умолчанию при docker runExec-форма ["программа", "арг1"] — без оболочки /bin/sh

Что происходит при docker build -t hello:1 .:

  1. Docker скачивает alpine:3.19, если его ещё нет локально.
  2. Создаёт временный контейнер, выполняет echo → фиксирует слой.
  3. Записывает в образ метаданные CMD.
  4. Помечает результат тегом hello:1.

Сборка и проверка:

docker build -t hello:1 .
docker run --rm hello:1
ШагОжидаемый результат
buildВ конце Successfully tagged hello:1
runВ терминале строка Привет из контейнера!
docker ps после runПусто — контейнер уже завершился

Если не работает:

ОшибкаПричинаЧто сделать
Cannot connect to the Docker daemonDocker не запущенЗапустить Docker Desktop
unable to prepare contextНет прав на папкуОткрыть терминал в каталоге hello/
Пустой выводСтарая версия DockerОбновить Docker Desktop

2. Статический сайт на nginx

Задача: отдать HTML/CSS из папки public/ без Node, Python и без npm run build.
Поиск: nginx dockerfile static site, dockerfile copy html nginx.

Смысл простыми словами: образ — это nginx + ваши файлы. Браузер запрашивает страницу → nginx читает файл с диска внутри контейнера → отдаёт HTML. Подходит для лендинга, учебного сайта, отчёта по вебу.

Структура:

static-site/
├── public/
│ └── index.html
├── Dockerfile
└── .dockerignore

public/index.html:

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Статика в Docker</title>
</head>
<body>
<h1>Сайт из Docker</h1>
<p>Файл лежит в public/index.html и копируется в образ.</p>
</body>
</html>

Dockerfile:

FROM nginx:1.27-alpine

COPY public/ /usr/share/nginx/html/

EXPOSE 80

HEALTHCHECK CMD wget -qO- http://127.0.0.1/ || exit 1

Разбор по строкам:

СтрокаЧто делаетЗачем
FROM nginx:1.27-alpineБазовый образ с уже установленным nginxНе ставим nginx вручную через apt
COPY public/ /usr/share/nginx/html/Копирует содержимое public/ в стандартную папку статики nginxURL / → файл index.html
EXPOSE 80Пишет в метаданные «сервис слушает 80»Напоминание; порт на хост задаёт -p
HEALTHCHECK … wget …Раз в 30 с дергает главную страницуСтатус healthy в docker ps; в учебе можно убрать

Что происходит при docker build:

  1. Слой nginx (из Hub).
  2. Новый слой — ваши HTML-файлы поверх /usr/share/nginx/html/.
  3. Образ готов; nginx не запущен до docker run.

Сборка и проверка:

docker build -t static:1 .
docker run --rm -p 8080:80 static:1
ДействиеРезультат
Открыть http://localhost:8080Заголовок «Сайт из Docker»
docker run без -pСайт не откроется с ПК — порт не проброшен

Разбор -p 8080:80:

ЧислоГде
8080Порт на вашем компьютере (localhost)
80Порт внутри контейнера, где слушает nginx

Тот же nginx одной строкой в Compose — стек №1.

Если не работает:

СимптомПричина
403 ForbiddenНет index.html или неверный путь COPY
Пустая страница Welcome to nginxCOPY не туда — проверьте public/
Connection refusedЗабыли -p или занят порт 8080 — смените на -p 8888:80

3. Node.js API (Express / Fastify)

Задача: упаковать REST API в образ для production: без dev-зависимостей, с кэшем npm и не от root.
Поиск: node dockerfile example, node dockerfile multistage npm ci, express dockerfile production.

Смысл простыми словами: две стадии сборки. На первой ставят node_modules, на второй — только готовые файлы и зависимости для запуска. Компиляторы и eslint в финальный образ не попадают. Это самый частый запрос среди студентов на fullstack-курсах.

Структура:

my-api/
├── package.json
├── package-lock.json
├── src/
│ └── server.js
├── Dockerfile
└── .dockerignore

Минимальный src/server.js (чтобы пример завёлся):

const http = require('http');

const server = http.createServer((req, res) => {
if (req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('ok');
return;
}
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Node API в Docker работает\n');
});

// 0.0.0.0 — слушать все интерфейсы; иначе с хоста не достучаться
server.listen(3000, '0.0.0.0', () => {
console.log('Слушаю http://0.0.0.0:3000');
});

Dockerfile:

FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

COPY --from=deps /app/node_modules ./node_modules
COPY package.json ./
COPY src ./src

RUN addgroup -S app && adduser -S app -G app
USER app

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget -qO- http://127.0.0.1:3000/health || exit 1

CMD ["node", "src/server.js"]

Разбор по строкам:

СтрокаЧто делаетЗачем
FROM node:20-alpine AS depsСтадия 1 — только установка зависимостейИмя deps для COPY --from=deps
WORKDIR /appДальше все пути относительно /appКак cd /app в терминале
COPY package.json package-lock.json ./Копирует только манифестыПри правке server.js слой npm ci берётся из кэша
RUN npm ci --omit=devСтавит пакеты строго по lock-файлуci = как в CI; --omit=dev без jest/eslint
FROM node:20-alpine AS runnerСтадия 2 — чистый runtimeВ финале нет мусора стадии deps
ENV NODE_ENV=productionПеременная для Node и библиотекМеньше отладочного режима
COPY --from=deps … node_modulesЗабирает папку из другой стадииКлюч multi-stage
COPY src ./srcИсходники после зависимостейПравка кода не перезапускает npm ci
adduser + USER appПроцесс не rootТребование безопасности в отчётах
EXPOSE 3000Документирует порт APIСвязка с -p …:3000
HEALTHCHECK … /healthПроверка эндпоинта из server.jsБез /health контейнер станет unhealthy
CMD ["node", "src/server.js"]Запуск при docker runExec-форма — корректные сигналы остановки

Что происходит при docker build:

  1. Стадия deps: слои COPY package*RUN npm ci (долго только при смене lock-файла).
  2. Стадия runner: копирование node_modules из deps, затем src.
  3. Слой USER app — все последующие команды от непривилегированного пользователя.
  4. В финальный образ не входят исходники стадии deps, кроме node_modules.

Сборка и проверка:

docker build -t my-api:1 .
docker run --rm -p 3000:3000 my-api:1

В другом терминале:

curl http://localhost:3000/health
curl http://localhost:3000/
КомандаОжидаемый ответ
/healthok
/Node API в Docker работает

Если не работает:

СимптомПричинаРешение
curl с хоста пустой / timeoutСервер слушает 127.0.0.1В коде listen(3000, '0.0.0.0')
npm ci падаетНет package-lock.jsonВыполнить npm install локально и закоммитить lock
EACCES при стартеПрава на файлыПеред USER добавить chown -R app:app /app
unhealthyНет /healthДобавить маршрут или убрать HEALTHCHECK

Манифесты npm — Манифесты зависимостей. API + Postgres в Compose — стек app + db.


4. Python (Flask / FastAPI)

Задача: запустить веб-приложение Python через gunicorn (production), а не через flask run.
Поиск: python dockerfile example, flask dockerfile requirements.txt, fastapi dockerfile.

Смысл простыми словами: образ содержит интерпретатор Python, установленные pip-пакеты и ваш app.py. При docker run стартует gunicorn — он принимает HTTP и передаёт запросы во Flask. Порядок COPY requirements.txtpip installCOPY . . экономит время при каждой правке кода.

Структура:

flask-app/
├── app.py
├── requirements.txt
├── Dockerfile
└── .dockerignore

app.py (минимум):

from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
return "Flask в Docker\n"

@app.route("/health")
def health():
return "ok"

requirements.txt:

flask==3.0.0
gunicorn==21.2.0

Dockerfile:

FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
APP_HOME=/app

WORKDIR $APP_HOME

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

RUN adduser --disabled-password --gecos '' appuser && \
chown -R appuser:appuser $APP_HOME
USER appuser

EXPOSE 5000

CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "app:app"]

Разбор по строкам:

СтрокаСмысл
python:3.12-slimОфициальный образ Python; slim меньше, чем полный
PYTHONDONTWRITEBYTECODE=1Не создавать .pyc в образе
PYTHONUNBUFFERED=1print и логи сразу в docker logs
APP_HOME=/appПеременная для пути; удобно менять одно место
WORKDIR $APP_HOMEРабочая директория /app
COPY requirements.txt + RUN pip installСлой зависимостей отдельно от кода
COPY . .Весь проект после pip
adduser / chown / USER appuserФайлы принадлежат пользователю, не root
gunicorn --bind 0.0.0.0:5000Слушать снаружи контейнера
--workers 2Два процесса — для учебы достаточно
app:appМодуль app.py, объект Flask app

Таблица кэша (как в отчёте по Docker):

Изменили файлЧто пересобирается
Только app.pyСлои начиная с COPY . .
requirements.txtpip install и всё ниже
DockerfileЧасто вся сборка

Сборка и проверка:

docker build -t flask-app:1 .
docker run --rm -p 5000:5000 flask-app:1
curl http://localhost:5000/
curl http://localhost:5000/health

FastAPI — замените зависимости и CMD:

fastapi==0.110.0
uvicorn[standard]==0.27.0
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "5000"]

Если не работает:

ОшибкаПричина
ModuleNotFoundError: flaskНет строки в requirements.txt
Failed to find attribute appВ app.py объект должен называться app
Порт занятdocker run -p 5001:5000

5. Go-сервис (multi-stage)

Задача: собрать один бинарник и положить его в крошечный образ без Go SDK.
Поиск: golang dockerfile multistage, go dockerfile alpine, dockerfile go build.

Смысл простыми словами: на стадии builder компилятор Go превращает исходники в файл /server. В финальный образ копируется только этот файл — ни go.mod, ни исходников, ни компилятора. Образ на диске часто 15–25 МБ вместо сотен.

Структура:

go-service/
├── go.mod
├── go.sum
├── cmd/
│ └── server/
│ └── main.go
└── Dockerfile

Минимальный cmd/server/main.go:

package main

import (
"fmt"
"net/http"
)

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Go сервис в Docker")
})
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "ok")
})
http.ListenAndServe(":8080", nil)
}

Dockerfile:

FROM golang:1.22-alpine AS builder
WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /server ./cmd/server

FROM alpine:3.19
RUN adduser -D appuser
USER appuser

COPY --from=builder /server /server

EXPOSE 8080
CMD ["/server"]

Разбор по строкам:

СтрокаСмысл
golang:1.22-alpine AS builderОбраз с компилятором Go
COPY go.mod go.sum + go mod downloadКэш модулей до копирования исходников
CGO_ENABLED=0Бинарник без привязки к C-библиотекам хоста
GOOS=linuxЦелевая ОС внутри контейнера
-ldflags="-s -w"Убрать отладочные символы — файл меньше
-o /server ./cmd/serverИмя бинарника и пакет с main
FROM alpine:3.19Финал — только Linux + ваш бинарник
COPY --from=builder /server /serverЕдинственный артефакт из стадии сборки
CMD ["/server"]Запуск бинарника как PID 1

Сборка и проверка:

docker build -t go-svc:1 .
docker run --rm -p 8080:8080 go-svc:1
curl http://localhost:8080/health

Если не работает:

ОшибкаРешение
go: cannot find main moduleВыполнить go mod init в корне проекта
no Go filesПуть ./cmd/server должен содержать package main

Distroless-вариант — энциклопедия, Go.


6. React / Vue SPA + nginx

Задача: собрать фронт (npm run build), раздавать статику через nginx; маршруты React Router работают при обновлении страницы.
Поиск: react dockerfile nginx, vite dockerfile production, spa nginx try_files docker.

Смысл простыми словами: браузеру нужны только файлы из dist/ (HTML, JS, CSS). Node.js в production не обязателен — он нужен лишь на этапе сборки. Поэтому два этапа: builder (Node) и runner (nginx).

Структура:

frontend/
├── package.json
├── package-lock.json
├── vite.config.js
├── index.html
├── src/
├── nginx.conf
└── Dockerfile

nginx.conf:

server {
listen 80;
root /usr/share/nginx/html;
index index.html;

location / {
try_files $uri $uri/ /index.html;
}
}

Разбор nginx (зачем в лабораторной):

СтрокаСмысл
root /usr/share/nginx/htmlСюда Dockerfile копирует dist/
try_files $uri $uri/ /index.htmlЕсли файла нет (маршрут /about) — отдать index.html, React дорисует страницу

Dockerfile:

FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:1.27-alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/dist /usr/share/nginx/html

EXPOSE 80
HEALTHCHECK CMD wget -qO- http://127.0.0.1/ || exit 1

Разбор по строкам:

СтрокаСмысл
npm ci + npm run buildСборка фронта в стадии builder
COPY --from=builder /app/distВ nginx попадает только результат сборки
nginx.confconf.d/default.confПодмена дефолтного виртуального хоста
Нет CMDВ образе nginx уже задана команда запуска

Сборка и проверка:

docker build -t spa:1 .
docker run --rm -p 8080:80 spa:1

Откройте http://localhost:8080. Для React Router зайдите на вложенный путь (если есть роуты) — без try_files будет 404 от nginx.

Если не работает:

СимптомПричина
Пустая страницаНеверная папка — у Vite это dist, у Create React App тоже часто dist
404 на /aboutНет try_files в nginx.conf
npm run build падает в DockerНе хватает памяти — закройте лишние программы

Подробнее proxy и TLS — Nginx — конфиги. Компоненты React — галерея React.


7. Java Spring Boot (JAR)

Задача: собрать fat JAR в образе с JDK, запускать на JRE.
Поиск: spring boot dockerfile example, dockerfile maven spring boot.

Смысл простыми словами: Spring Boot упаковывает приложение в один .jar со встроенным Tomcat. В Dockerfile сначала Maven собирает JAR, потом в лёгкий образ копируется только app.jar и команда java -jar.

Структура:

spring-app/
├── pom.xml
├── mvnw
├── .mvn/
├── src/
└── Dockerfile

Dockerfile:

FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY pom.xml mvnw ./
COPY .mvn .mvn
COPY src ./src
RUN chmod +x mvnw && ./mvnw -q -DskipTests package

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app

RUN addgroup -S app && adduser -S app -G app
USER app

COPY --from=builder /app/target/*.jar app.jar

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD wget -qO- http://127.0.0.1:8080/actuator/health || exit 1

ENTRYPOINT ["java", "-jar", "app.jar"]

Разбор по строкам:

СтрокаСмысл
eclipse-temurin:21-jdk-alpineJDK для компиляции
./mvnw … packageСборка JAR без установленного Maven на ПК
-DskipTestsБыстрее для учебной сборки
jre-alpine в финалеТолько среда выполнения, без компилятора
COPY … *.jar app.jarОдин понятный файл в runtime
/actuator/healthЭндпоинт Spring Actuator (нужна зависимость в pom.xml)
ENTRYPOINT ["java", "-jar", "app.jar"]Команда запуска; аргументы docker run дописываются в конец

Сборка и проверка:

docker build -t spring:1 .
docker run --rm -p 8080:8080 spring:1

Первая сборка может занять 5–15 минут — Maven скачивает зависимости.

Если не работает:

ОшибкаРешение
no main manifestВ pom.xml должен быть spring-boot-maven-plugin
Health 404Добавить spring-boot-starter-actuator или убрать HEALTHCHECK
Несколько JAR в target/Уточнить имя: COPY …/myapp-0.0.1-SNAPSHOT.jar app.jar

8. PHP с Apache

Задача: быстрый учебный сайт на PHP без настройки php-fpm и второго контейнера.
Поиск: php dockerfile apache, dockerfile php website.

Смысл простыми словами: образ php:8.3-apache уже содержит веб-сервер Apache и модуль PHP. Вы копируете .php файлы в /var/www/html/ — Apache выполняет PHP и отдаёт результат браузеру.

Структура:

php-site/
├── public/
│ └── index.php
└── Dockerfile

public/index.php:

<?php
header('Content-Type: text/plain; charset=utf-8');
echo "PHP " . PHP_VERSION . " в Docker\n";
echo "Время: " . date('c') . "\n";

Dockerfile:

FROM php:8.3-apache

RUN docker-php-ext-install pdo pdo_mysql

COPY public/ /var/www/html/

EXPOSE 80

Разбор по строкам:

СтрокаСмысл
php:8.3-apachePHP 8.3 + Apache в одном образе
docker-php-ext-install pdo pdo_mysqlРасширения для лабораторных с MySQL
COPY public/ /var/www/html/index.php доступен как http://…/
EXPOSE 80Apache слушает 80 внутри контейнера

Сборка и проверка:

docker build -t php-site:1 .
docker run --rm -p 8080:80 php-site:1

В браузере — версия PHP и время.

Если не работает:

СимптомПричина
Скачивается файл вместо выполненияФайл не .php или не в public/
403Нет index.php в /var/www/html

Production-вариант — nginx + php-fpm в двух сервисах: Nginx PHP-FPM, WordPress Compose.


9. ASP.NET Core

Задача: собрать и опубликовать .NET 8 Web API в компактном runtime-образе.
Поиск: aspnet core dockerfile, dotnet dockerfile multistage.

Смысл простыми словами: стадия sdk компилирует проект (dotnet publish), стадия aspnet только запускает готовые DLL. Kestrel слушает порт из ASPNETCORE_URLS.

Структура:

dotnet-api/
├── DotnetApi.csproj
├── Program.cs
└── Dockerfile

Dockerfile:

FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
WORKDIR /src
COPY DotnetApi.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false

FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
WORKDIR /app

RUN addgroup -S app && adduser -S app -G app
USER app

COPY --from=build /app/publish .

ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget -qO- http://127.0.0.1:8080/health || exit 1

ENTRYPOINT ["dotnet", "DotnetApi.dll"]

Разбор по строкам:

СтрокаСмысл
COPY DotnetApi.csproj + dotnet restoreКэш NuGet до копирования всего кода
dotnet publish … -o /app/publishГотовые файлы для запуска
UseAppHost=falseЗапуск через dotnet MyApp.dll (проще в Linux-контейнере)
aspnet:8.0-alpineRuntime без SDK
ASPNETCORE_URLS=http://+:8080Kestrel на всех интерфейсах, порт 8080
DotnetApi.dllИмя = имя проекта в .csproj

Сборка и проверка:

docker build -t dotnet-api:1 .
docker run --rm -p 8080:8080 dotnet-api:1

В Program.cs для учебы:

app.MapGet("/", () => "ASP.NET в Docker");
app.MapGet("/health", () => "ok");

Если не работает:

ОшибкаРешение
could not find DotnetApi.dllИмя DLL = имя .csproj
Connection refused с хостаПроверить ASPNETCORE_URLS и -p 8080:8080

10. Job-контейнер (миграции или seed)

Задача: контейнер запускает скрипт и завершается — миграции БД, загрузка тестовых данных, ночной batch.
Поиск: dockerfile run script once, docker batch job example.

Смысл простыми словами: это не веб-сервер. Вы ждёте код выхода 0 (успех) и статус Exited (0) в docker ps -a. В Kubernetes тот же паттерн — ресурс Job.

Структура:

db-job/
├── migrate.sh
└── Dockerfile

migrate.sh:

#!/bin/sh
set -e
echo "Подключение: $DATABASE_URL"
# Раскомментируйте для реальной БД:
# psql "$DATABASE_URL" -f ./migrations/001_init.sql
echo "Миграции применены"

Dockerfile:

FROM alpine:3.19
RUN apk add --no-cache postgresql-client bash
WORKDIR /job
COPY migrate.sh .
RUN chmod +x migrate.sh
USER nobody
ENTRYPOINT ["./migrate.sh"]

Разбор по строкам:

СтрокаСмысл
apk add postgresql-clientУтилита psql для SQL
chmod +x migrate.shСкрипт исполняемый
USER nobodyДаже одноразовая задача не от root
ENTRYPOINT ["./migrate.sh"]При docker run всегда стартует скрипт
Нет EXPOSEСетевой сервер не поднимается

Запуск рядом с Postgres из Compose:

docker build -t db-job:1 .
docker run --rm \
-e DATABASE_URL=postgres://app:secret@db:5432/appdb \
--network myproject_default \
db-job:1
ПараметрСмысл
-e DATABASE_URL=…Пароль не в образе, только при запуске
--network …Имя db резолвится в IP контейнера PostgreSQL
--rmУдалить контейнер после успеха

Имя сети смотрите: docker network ls после docker compose up из галереи Compose.

Проверка: в выводе Миграции применены; docker ps -a — контейнер Exited (0).


Слои и кэш — одна таблица на все примеры

ПравилоПример
Редко меняющееся — вышеCOPY package.json перед COPY src
Тяжёлое — отдельный слойRUN npm ci, RUN pip install
Секреты — не в образ.env в .dockerignore, -e при run
Фиксируйте версииnode:20-alpine, не node:latest
Меньше финальный образmulti-stage для Go, Node, Java, .NET, SPA

Проверить слои:

docker history my-api:1 --no-trunc

Частые ошибки (все примеры)

ОшибкаЧто значитЧто проверить
COPY failed: file not foundНет файла в контекстеПуть, .dockerignore, вы в нужной папке
port is already allocatedПорт занят на хостеДругой -p 8888:3000
Сайт не открываетсяНет проброса порта-p хост:контейнер
API не отвечает с ПКСлушает только localhost0.0.0.0 в коде
permission deniedUSER без прав на файлыchown перед USER
Огромный образВсё в одной стадииMulti-stage, .dockerignore

Чек-лист перед сдачей лабораторной

  • В Dockerfile зафиксированы теги (node:20-alpine), не latest.
  • Есть .dockerignore, нет .env в образе.
  • Зависимости копируются до исходников.
  • В README — docker build, docker run, URL или curl для проверки.
  • Для HTTP указаны EXPOSE и -p.
  • Скриншот docker ps или ответа в браузере приложен к отчёту.

Связанные материалы

МатериалЗачем
Dockerfile (теория)все инструкции, анти-паттерны
Dockerbuild, run, push
Docker Compose — готовые стекиbuild: . + БД + nginx
Nginx — конфигиSPA, proxy, PHP-FPM
Шаблоныминимальный Dockerfile на одной странице
GitHub Actions — CI/CDdocker build в pipeline
Примеры Turtleтот же формат галереи для Python

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").