Как объединить фронтенд и бэкенд в одном приложении? Деплой pet-проекта
В инструкции рассказываем, как написать Docker-файл и настроить конфигурацию nginx, чтобы задеплоить веб-приложение на облачный сервер.
Введение
Привет! Меня зовут Артём Шумейко, я Python-разработчик и создатель одноименного канала на YouTube. Представьте, у вас есть проект с фронтенд- и бэкенд-частью. Первая работает на одном порту и отображает данные, после — передает бэкенду. Вторая функционирует на другом порту, принимает и обрабатывает эти данные, после чего возвращает ответ. Обычно сайты находятся на едином домене с фронтендом и бэкендом, а здесь — на двух отдельных.
Будучи новичком я не понимал, как объединить фронтенд и бэкенд. Думал, нужно подключать два домена и неведомым образом их «подружить». Но все оказалось намного проще. В тексте расскажу о двух способах развертывания приложения и поделюсь подробной инструкцией.
О проекте
В инструкции не будем писать веб-приложение с нуля — возьмем готовый проект, который состоит из 30-40 строк кода. Фронтенд будет отображать данные с бэкенда: плашку, items, картинки и данные с GitHub, Docker, nginx.
@app.get("/items")
def get_items():
items = [
{
"id": 1,
"name": "Docker",
"img": "https://static-00.iconduck.com/assets.00/docker-icon-2048x2048-5mc7mvtn.png",
},
{
"id": 2,
"name": "Nginx",
"img": "https://www.svgrepo.com/show/373924/nginx.svg",
},
{
"id": 3,
"name": "GitHub",
"img": "https://cdn-icons-png.flaticon.com/512/25/25231.png",
},
]
random.shuffle(items)
return items
Весь проект локально запущен в двух терминалах. В первом — бэкенд с портом 8000, во втором — фронтенд с портом 5173. По сути, это два разных сервера, которые нужно соединить.
Чтобы объединить фронтенд и бэкенд, есть две стратегии. Первый способ — это реверс-прокси (обратный прокси), при котором мы не создаем домен. В нем разворачиваем фронтенд на основной сайт, а бэкенд — на адрес/API
. Второй — это использование обратного прокси через создание поддомена. В нем создаем еще один домен к основному, например api.mysite.ru
, чтобы разделять сайты. В тексте будем использовать обратный прокси без поддомена.
Настройка Docker-файла
Бэкенд
Нужно написать два простых Docker-файла, чтобы перенести данные в контейнеры. В образ Python добавляем slim
, чтобы не занимать много места:
FROM python:3.11.9-slim
Перемещаем все файлы в локальную директорию и добавляем зависимости в requirements.txt. После копируем файлы в Docker-образ:
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
Сначала устанавливаем файлы с зависимостями, которые редко меняются, и только потом остальные. Запускаем код:
CMD [ "python", "main.py" ]
Фронтенд
Копируем весь Docker-файл и добавляем его в папку frontend. В нем будем использовать не Python, а JavaScript и Node.js.
Указываем последнюю версию ноды alpine и называем ее build:
FROM node: alpine as build
Здесь зависимости находятся не в папке requirements.txt, а в package.json. Копируем их и добавляем в файл:
COPY package.json package.json
RUN npm install
После — копируем оставшиеся файлы:
COPY . .
RUN npm run build
Фронтенд поддерживает только статические файлы: JavaScript, HTML и CSS. Чтобы забрать их из контейнера, запускаем nginx прямо в коде:
FROM nginx: stable-alpine
После команды npm build
внутри Docker-образа появилась папка /dist. В ней хранятся созданные билды, а именно — статические файлы. Отправляем их в nginx:
COPY --from=build /dist /usr/share/nginx/html
Далее копируем nginx.conf в классическую директору nginx:
COPY --from=build nginx.conf /etc/nginx/conf.d/default.conf
Указываем порт, на котором будет работать nginx, через EXPOSE 3000, но можно выбрать другой:
EXPOSE 3000
CMD [ "nginx", "-g", "daemon off;" ]
Чтобы отдавать файлы index.html, создаем nginx.conf:
server {
listen 3000;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html =404;
}
include /etc/nginx/extra-conf.d/*.conf;
}
Общая сборка приложения
Создаем файл docker-compose, чтобы управлять сразу несколькими контейнерами. Здесь используем контейнер бэкенда и фронтенда в YAML-формате.
services:
backend:
build:
context: ./backend
frontend:
build:
context: ./frontend
Перед контейнерами запускаем nginx из готового образа DockerHub:
services:
nginx:
image: nginx:stable-alpine
ports:
- "80:80"
backend:
build:
context: ./backend
frontend:
build:
context: ./frontend
Указываем стандартный порт HTTP — 80:80.
Далее нужно выполнить две задачи:
- настроить приложение так, чтобы nginx запускался только после бэкенда и фронтенда;
- добавить volumes, чтобы nginx видел файлы с локального сервера.
services:
nginx:
image: nginx:stable-alpine
ports:
- "80:80"
volumes:
- './nginx.conf:/etc/nginx/nginx.conf'
depends_on:
- backend
- frontend
Соединяем все контейнеры в единую сеть и указываем зависимости:
services:
nginx:
image: nginx:stable-alpine
ports:
- "80:80"
volumes:
- './nginx.conf:/etc/nginx/nginx.conf'
depends_on:
- backend
- frontend
networks:
- dev
backend:
build:
context: ./backend
networks:
- dev
frontend:
build:
context: ./frontend
networks:
- dev
Настройка конфигурации nginx
Используем готовый файл nginx.conf. Называем пользователя root
, добавляем необходимое количество worker-нод и порт 80.
user root;
worker_processes 1;
events {
}
http {
server {
listen 80;
server_name localhost
}
}
Указываем Location
, чтобы при запуске выводить фронтенд на главную страницу. После — добавляем proxy_pass
, чтобы проксировать запросы через nginx, и порт 3000, как на нашем сервере.
location / {
proxy_pass http://frontend:3000/;
}
Далее подключаем бэкенд и добавляем в Location /api/
. Порт — 8000, поскольку контейнер с бэкендом использует именно его.
location /api/ {
proxy_pass http://backend:8000/;
}
Конфигурация nginx готова.
Создание облачного сервера
Переходим в панель управления Selectel и создаем новый сервер. Указываем имя, регион и операционную систему — в нашем случае Ubuntu 24.04 LTS 64-bit. Для работы сайта подойдет минимальная конфигурация:
- 1 CPU,
- 1 ГБ RAM,
- 10 ГБ SSD,
- публичный IP-адрес.
Далее добавляем SSH-ключ. Указываем имя и копируем команду для терминала. После получаем публичный ключ и добавляем его в окно создания.
Теперь можно автоматически подключаться к серверу без использования пароля, с помощью команды ssh root@<IP-адрес сервера>. Нажимаем Создать сервер.
Создание репозитория
Создаем новый репозиторий для хранения кодовой базы. Если проект не конфиденциален и вы хотите клонировать его на сервер без дополнительной авторизации, сделайте репозиторий публичным. Далее — перейдите в терминал (предварительно не забудьте подключиться к серверу) и клонируйте репозиторий с помощью команды git clone <адрес репозитория>.
Поскольку у нас около 20 файлов, создаем файл .gitignore. Туда добавляем файлы с зависимостями — venv
и node-modules
. Пишем initial commit
, чтобы отправить их в Git.
venv/
node_modules/
Переходим в терминал и обновляем пакеты, чтобы установить Git. Создаем клон и скачиваем код по HTTPS с GitHub. На выходе получаем папку test-deploy-site, в которой находятся все файлы.
Деплой веб-приложения на сервер
Теперь нужно установить Docker. Используем готовый код — копируем и добавляем его в терминал.
Собираем все образы и добавляем их в контейнеры Docker:
docker compose up --build
Адрес бэкенда и фронтенда скрыты от конечного пользователя и мошенников. Напрямую с ними работать не получится, только через nginx.
Заключение
Нам удалось соединить фронтенд с бэкендом в одном веб-приложении и развернуть его на облачном сервере. Оставляю код на GitHub и видеоверсию инструкции, чтобы вы могли легко повторить все шаги.
Автор: Артём Шумейко, создатель YouTube-канала.