Привет, Хабр! Сегодня поговорим об одном интересном микро-фреймворке для Python — Flask. Мы создадим свое собственное веб-приложение и изучим расширения flask, а после задеплоим его на сервер, чтобы иметь доступ из внешнего мира.
Flask всегда мне нравился, ибо он был минималистичный, быстрый, лёгкий для изучения, и в то же время легко расширялся до полноценного проекта.
Мы затронем все моменты, я объясняю каждую строчку кода. Мы будем создавать не просто какой то статичный сайт — а открытую публичную стену, с регистрацией и авторизацией. Каждый может туда зайти, авторизоваться и оставлять посты на общедоступной стене.
А самое главное — безболезненный, быстрый и легкий деплой будущего приложения.
Наш проект я назвал Open Wall. Оригинально, но вы можете назвать как хотите, главное название поменяйте.
Чтобы понимать, о чем мы будем разговаривать, вам требуется знать сам язык python, язык разметки HTML и CSS. Без этих базовых знаний вы мало что поймете.
По моему концепту, Open Wall будет состоять из следующего функционала:
- Регистрация пользователя
- Авторизация пользователя
- Создание постов на общей стене
- Профили пользователей
Open Wall будет иметь базовый функционал. Если проект станет популярным — может быть, я улучшу его, и сделаю новый туториал на тему улучшения. Например, добавление flask-admin.
Если вы хотите сразу опробовать его — перейдите на сам сайт или в его репозиторий.
За дизайн сайта не ругайте, вы вольны его изменить как хотите, я быстро сделал легкий, адаптивный сайт.
Итак, допустим вы хотите сделать проект с самого начала. Тогда следуйте дальнейшим шагам для создания базового виртуального окружения. Виртуальное окружение позволит изолировать ваш проект от остальных. Все команды будут указаны для Linux.
python3 -m venv venv && source venv/bin/activate && pip3 install flask gunicorn flask_login flask_sqlalchemy && pip3 freeze > requirements.txt
Этой командой мы: а) создали виртуальное окружение и активировали его; б) установили зависимости — сам flask и gunicorn для запуска его на сервере; в) «заморозили» зависимости в файле, чтобы после можно было их установить на сервер.
После создаем базовую архитектуру:
- Создайте директорию templates, там будут храниться шаблоны.
- Создайте директорию static/css. static — это специальная директория для хранения статичных файлов, например style.css в папке css.
- Создайте главный файл приложения — app.py, где мы будем писать код.
Итак, давайте напишем само приложение:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
Давайте разберем каждую строчку:
- from flask import Flask, render_template. Этой командой мы импортировали приложение Flask и функцию для рендера Jinja2-шаблона. О шаблонах поговорим позже.
- app = Flask(__name__). Создаем приложение.
- app.route('/'). Это декоратор для рендера пути сайта — в данном случае это корень, /. Дальше идет функция, где мы возвращаем шаблон.
- if __name__ == '__main__'. Это специальная строка, которая позволяет выполнять код, если скрипт исполняется напрямую (не импортируется).
- app.run(debug=True). Запускаем приложение с включенным дебагом. Также можно задать host, port.
❯ Шаблоны
В Flask используется шаблонизатор Jinja. Это быстрая программа, которая позволяет создавать HTML-шаблоны. Вот как выглядит базовый шаблон base.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
Здесь вы можете увидеть фигурные скобки с процентами — они используются если нужны какие-то функции, например цикл for:
{% for item in items %}
{{ item }}
{% endfor %}
Также есть просто двойные фигурные скобки — они нужны, например, для переменных.
❯ База данных и авторизация
Flask — это микрофреймворк, поэтому база данных может быть любая, даже просто встроенный модуль sqlite3. Но мы будем использовать специальное расширение — Flask SQLAlchemy. Это ORM модуль, с возможностью подключения различных БД — от sqlite до MySQL. Мы будем использовать sqlite.
Вот пример модели пользователя для БД:
from datetime import datetime
from flask import Flask, render_template, url_for, request, redirect, flash, get_flashed_messages
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, current_user, login_user, logout_user, login_required
from werkzeug.security import generate_password_hash, check_password_hash
from sqlalchemy import desc
app = Flask(__name__)
login = LoginManager(app)
login.login_view = 'login'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///wall.db'
app.config['SECRET_KEY'] = 'секретный ключ'
db = SQLAlchemy(app)
@login.user_loader
def load_user(id):
return db.session.get(User, int(id))
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer(), primary_key=True)
title = db.Column(db.String(255), nullable=False)
content = db.Column(db.Text(), nullable=False)
created_on = db.Column(db.DateTime(), default=datetime.utcnow)
author = db.Column(db.Integer(), nullable=False)
def __repr__(self):
return "<{}:{}>".format(self.id, self.title[:10])
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(63), index=True, unique=True)
password_hash = db.Column(db.String(127))
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def __repr__(self):
return '<{}:{}>'.format(self.id, self.username[:10])
Давайте разберем код подробнее:
- Мы импортируем модуль datetime — для взаимодействия с датой и временем в БД.
- Мы импортируем из модуля Flask некоторые классы и функции — Flask, render_template, url_for (создание URL для сайта), request — получаем информацию о запросе, нужно при создании форм, redirect — редирект на URL на сайте, flash — создание быстрых flash-сообщений (например о том, что пароль при входе в аккаунт неверный), get_flashed_messages — получаем flash-сообщения, чаще всего используется в шаблоне.
- Импортируем из модуля flask_login классы и функции для входа и регистрации. Flask Login — это расширение, которое упрощает разработчику создание методов входа и регистрации. Мы импортируем LoginManager — менеджер входами, UserMixin — специальный класс, который мы наследуем для модели пользователя, чтобы можно было работать с текущим пользователем, current_user — при помощи его мы сможем использовать данные текущего пользователя, например его ID, или просто проверить, вошел ли юзер в свой аккаунт. login_user позволяет авторизовать пользователя, logout_user — функция выхода из аккаунта и login_required — декоратор для создания страниц, для которых требуется логин.
- Следующий шаг — импорт модуля Werkzeug, а точнее двух функций — generate_password_hash и check_password_hash. В любых БД пароли хранят в хешированном виде, а иногда даже с солью — случайным набором букв, ибо если взломают БД, то злоумышленники могут увидеть пароли. А хешированные пароли тяжело взломать — особенно если используются новые безопасные алгоритмы. Можно конечно найти коллизию — это когда хеш одной фразы и хеш другой совпадают, но фразы разные. Но найти коллизии очень трудно, практически невыполнимо.
- И последний импорт — это desc из sqlalchemy. Простая функция, которая нам нужна, когда мы будем сортировать посты по времени создания.
После идет создание экземпляров классов и настройка:
- app = Flask(__name__). Здесь мы создаем веб-приложение
- login = LoginManager(app). LoginManager позволяет нам управлять логинами, осуществлять авторизацию и работать с текущим пользователем
- Дальше мы задаем для менеджера авторизации, куда пересылать пользователя когда он будет заходить на страницу, которая доступна только авторизованным пользователем. О пути login мы поговорим ниже
- Следующая строка задает параметр SQLALCHEMY_DATABASE_URI — это путь до базы данных
- После мы также конфигурируем приложение, и задаем секретный ключ. Без этого ключа приложение не будет работать.
- И в конце мы вызываем экземпляр класса SQLAlchemy.
Теперь давайте создадим некоторые нужные нам функции — это загрузка пользователя и выход из аккаунта:
- Первая функция — load_user, с декоратором login.user_loader. Это функция загружает пользователя из БД по его ID
- Вторая функция — logout. Это функция, которая вызывает функцию logout_user (выход из аккаунта) из flask_login, и после создает редирект на главную страницу.
Настала очередь создать две модели для базы данных — это модель поста:
- Мы создаем класс Post, наследуя его от базового класса db.Model.
- Мы задаем название таблицы, в нашем случае — posts.
- Мы записываем id — это будет целочисленное значение, которое будет автоматически задаваться, то есть будет проходить инкрементирование значения.
- Далее — title, заголовок. Это будет строковое значение с максимальной длиной 255 символов. Он должен быть обязательно (nullable).
- После мы задаем параметр content — контента статьи. Он также обязательно должен быть (параметр nullable).
- Предпоследнее значение — дата создания поста, дефолтное значение — это дата и время по UTC, когда был создан пост.
- И последнее значение — это author, куда мы указываем ID автора (то есть ID текущего пользователя, current_user.id).
- Осталась только магическая функция __repr__. Она позволяет преобразовывать произвольные объекты в строковое значение.
И модель пользователя:
- Кроме наследования db.Model, мы наследуем специальный класс UserMixin, чтобы работать с Flask Login.
- Также, как и в модели поста мы создаем id, который автоматически увеличивается
- Дальше мы задаем имя пользователя, максимальная длина — 63.
- И в конце создание колонки хеша пароля — с максимальной длиной, нет, не 127, а 128 символов.
- Теперь нам требуется создать две вспомогательные функции — set_password и check_password. Первая преобразует пароль в хеш, а вторая проверяет пароль, который подан на аргументы и пароль из бд. Здесь мы как раз и задействуем модуль werkzeug.
- И мы также создаем магическую функцию __repr__, как и в модели пользователя.
Вот и первая часть кода позади. Мы создали уже основу, скелет приложения, но не хватает главного — самого функционала сайта, взаимодействия с БД.
❯ Создание путей отображения
Итак, начнем c функции index. Главная функция, отображает корневой каталог сайта.
@app.route('/')
@app.route('/index')
def index():
posts = Post.query.order_by(desc(Post.created_on)).all()
return render_template('index.html', posts=posts)
В функции index мы задаем два пути отображения — / и /index. После мы создаем список постов, обращаемся к модели Post, и требуем выдать все посты, отсортированные по дате создания. После мы делаем рендер шаблона, и передаем список постов аргументов, чтобы потом использовать его.
Давайте рассмотрим сам шаблон index.html и base.html. Сами шаблоны хранятся в директории templates. base.html — это базовый шаблон, от него наследуются уже все остальные. В нем мы задаем форму и создаем блоки. В общем, вот как выглядит он у меня:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Open Wall</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<div class="wrapper">
<div class="header">
<h1><a href="/" style='color: white; text-decoration: none;'>Open Wall - открытая публичная стена</a></h1>
<a href="https://github.com/AlexeevDeveloper/openwall">Репозиторий</a>
<a href="https://habr.com/ru/companies/timeweb/articles/812413/">Статья</a>
{% if not current_user.is_authenticated %}
<a href="/login">Войти</a>
<a href="/reg">Регистрация</a>
{% else %}
<a href="/new">Создать пост</a>
{% endif %}
</div>
<div class="content">
{% block content %}
{% endblock %}
</div>
<div class="footer">
Все что останется здесь - останется навсегда. Добро пожаловать на Open Wall!
</div>
</div>
</body>
</html>
Думаю, вы знаете HTML, но некоторые моменты надо прояснить. Как мы уже говорили, фигурные скобки с процентами — для функций шаблонизатора Jinja, а просто двойные фигурные скобки — для переменных.
Итак, в head-блоке, в элементе link, где мы задаем CSS стили, можно увидеть строку {{ url_for('static', filename='css/style.css') }}. Чтобы нам вручную не вводить путь, мы задаем путь через url_for (функция из Flask). Здесь мы задаем путь до стилей, лежат они в директории static. Вторым аргументом мы задаем путь до файла — css/style.css.
Если вы хотите увидеть стили (я не стал сюда их вставлять, ибо это заняло бы слишком много места) — то перейдите по ссылке.
Ниже мы видим конструкцию типа if-else, то есть условный оператор. Первой строкой мы узнаем, текущий пользователь авторизован или нет. Если нет, то мы выводим ссылки на вход и регистрацию, а иначе — одну ссылку на создание поста. В последней строке мы даем шаблонизатору понять, что конструкция закончилась.
И последнее — это блок контента. Чтобы мы могли наследовать и задавать контент, мы создадим блок, и в других шаблонах мы можем туда вписывать что нам нужно.
Теперь рассмотрим файл index.html:
{% extends 'base.html' %}
{% block content %}
{% for post in posts %}
<div class="posts">
<div class="post">
<div class="header">
<h2>{{ post.title }}</h2>
</div>
{{ post.content }}
<hr>
<a href="/profile/{{ post.author }}">Автор: {{ post.author }}
Дата: {{ post.created_on }}
</div>
</div>
{% endfor %}
{% endblock %}
- Командой extends 'base.html' мы говорим приложению наследовать наш базовый шаблон.
- После мы редактируем блок content — добавляем в него список постов.
- Дальше, в самом блоке контента, мы создаем цикл, и проходимся по всем элементам в списке posts (который мы передали в функции) и выводим: заголовок, контент, ссылку на профиль автора (рассмотрим позже) и дату создания поста.
- В конце мы обозначаем, что завершили цикл и блок.
Теперь займемся функцией регистрации:
@app.route('/reg', methods=['GET', 'POST'])
def reg():
if current_user.is_authenticated:
return redirect(url_for('index'))
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
password2 = request.form['password2']
if password != password2:
flash('Пароли не совпадают')
return render_template('reg.html')
if len(username) > 63:
flash('Длина имени пользователя не должна быть больше 63 символов')
return render_template('reg.html')
try:
user = User(username=username)
user.set_password(password)
db.session.add(user)
db.session.commit()
except Exception as e:
flash(f'Ошибка при создании пользователя: {e}')
return render_template('reg.html')
else:
login_user(user)
return redirect('index')
return render_template('reg.html')
Мы задаем путь отображения — это /reg. Также мы добавляем в декораторе параметр METHODS, который определяет принимаемые запросы — GET и POST.
После мы проверяем, авторизован ли пользователь. Если да, то отправляем его на главную страницу.
Следующий проверяет метод запроса — если это POST, то есть отправка данных, то мы начинаем регистрацию. Мы получаем username, пароль и повторение пароля из формы, проверяем пароли, проверяем длину имени пользователя и после мы создаем нового пользователя, автоматически его авторизуя в системе. При успешной регистрации перенаправляем на главную страницу, а при провале или при GET-запросе — рендерим шаблон reg.html:
{% extends 'base.html' %}
{% block content %}
<h2>Регистрация</h2>
<form method="POST">
{% for msg in get_flashed_messages() %}
<div class="error">
{{ msg }}
</div>
{% endfor %}
Введите имя пользователя:
<input type="text" name='username' placeholder="Имя пользователя"><br>
Введите пароль:
<input type="password" name='password' placeholder="Пароль"><br>
Повторите пароль:
<input type="password" name='password2' placeholder="Повторите пароль"><br>
<br>
<input type="submit" class='btn'>
<br>
</form>
{% endblock %}
В это шаблоне мы также используем функцию get_flashed_messages для отображения flash-сообщений.
Следующим шагом мы создадим функцию логина пользователя:
@app.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))
if request.method == 'POST':
user = db.session.query(User).filter(User.username == request.form['username']).first()
if user is not None:
if not user.check_password(request.form['password']):
flash('Пароль неверный')
return render_template('login.html')
login_user(user)
return redirect(url_for('index'))
else:
flash('Пользователя не существует. Проверьте имя пользователя или пароль.')
return render_template('login.html')
Также мы задаем типы методов, и также проверяем авторизован ли уже пользователь.
После мы проверяем, существует ли пользователь с таким именем в системе. Если он существует, то мы проверяем хеш паролей (если пароль неверный, выводим об этом сообщение). Если хеши совпали, то можно авторизовать пользователя и перевести его на главную страницу.
Шаблон страницы логина:
{% extends 'base.html' %}
{% block content %}
<h2>Вход в аккаунт</h2>
<form method="POST">
{% for msg in get_flashed_messages() %}
<div class="error">
{{ msg }}
</div>
{% endfor %}
Введите имя пользователя:
<input type="text" name='username' placeholder="Имя пользователя"><br>
Введите пароль:
<input type="password" name='password' placeholder="Пароль"><br>
<br>
<input type="submit" class='btn'>
<br>
</form>
{% endblock %}
Все практически также, как и при регистрации. Разве что без повторного ввода пароля.
Теперь давайте немного отдохнем, напишем несложную функцию отображения профиля:
@app.route('/profile/<username>')
def profile(username: str):
user = db.session.query(User).filter(User.username == username.first()
if user is None:
return redirect('index')
posts = db.session.query(Post).filter(Post.author == username).order_by(desc(Post.created_on)).all()
return render_template('profile.html', username=username, posts=posts)
В пути вы могли увидеть после второго слэша слово username. Это специальный аргумент, который будет означать произвольное слово после /profile/. В данном случае — пользователя. Этот аргумент также есть в функции.
Также, как и при логине мы проверяем, существует ли пользователь. Если нет, то делаем редирект на главную страницу. Если пользователь существует, мы собираем все посты данного пользователя и сортируем по дате, и после передаем username и posts в функцию render_template. А вот сам шаблон профиля:
{% extends 'base.html' %}
{% block content %}
<h2>Пользователь: {{ username }}</h2>
{% if current_user.username == username %}
<a href="/logout">Выйти из аккаунта</a>
{% endif %}
<h3 style='text-align: center;'>Посты</h3>
{% for post in posts %}
{% if post.author == username %}
<div class="posts">
<div class="post">
<div class="header">
<h2>{{ post.title }}</h2>
</div>
{{ post.content }}
<hr>
Дата: {{ post.created_on }}
</div>
</div>
{% endif %}
{% endfor %}
{% endblock %}
Здесь я добавил небольшую фичу — если пользователь перешел на страницу своего же аккаунта, то мы добавляем ссылку для выхода из профиля.
И наконец-то последняя функция — функция создания нового поста.
@login_required
@app.route('/new', methods=['GET', 'POST'])
def new_post():
if not current_user.is_authenticated:
return redirect('login')
if request.method == 'POST':
title = request.form['title']
content = request.form['content']
if len(title) > 0 and len(title) < 256 and len(content) > 0:
post = Post(title=title, content=content, author=current_user.username)
try:
db.session.add(post)
db.session.commit()
except Exception as e:
flash(f'Возникла ошибка при записи в базу данных: {e}')
else:
return redirect('index')
else:
flash('Ошибка, длина заголовка поста не соответствует стандартам. Максимальное количество символов заголовка - 255.')
return render_template('newpost.html')
return render_template('newpost.html')
Здесь как раз мы и используем декоратор login_required. И также мы, на случай обхода этого декоратора, создаем условие на проверку, авторизован ли пользователь. Если нет — то посылаем его на страницу логина.
Потом мы также, как и при регистрации, если метод POST получаем заголовок и контент поста, проверяем его на размер, создаем новую модель и отправляем его в БД.
Вот шаблон newpost.html:
{% extends 'base.html' %}
{% block content %}
<h2>Создание статьи</h2>
<form method="POST">
{% for msg in get_flashed_messages() %}
<div class="error">
{{ msg }}
</div>
{% endfor %}
Введите заголовок статьи
<input type="text" name='title' placeholder="Заголовок"><br>
Введите текст статьи
<textarea name="content" id=""></textarea>
<br>
<input type="submit" class='btn'>
<br>
</form>
{% endblock %}
❯ Финальные приготовления
В файл app.py (главный файл приложения), в самый конец, прописываем специальный условный оператор:
if __name__ == '__main__':
app.run(debug=True, port=5000)
Если программа запускается напрямую (не импортируется), то мы запускаем приложение с включенным дебагом и портом на 5000.
После мы создаем файл create_db.py, который будет создавать файл базы данных:
from app import app, db
with app.app_context():
db.create_all()
Мы импортируем app и db из файла приложения (app) и при помощи контекстного менеджера with вызываем метод create_all из db.
И давайте создадим последний файл — main.py, он и будет запускать наш сервер, именно через него gunicorn (специальный модуль для запуска фласк-приложения на сервере) будет запускать нашу стену:
from app import app
if __name__ == '__main__':
app.run(debug=False)
И наконец то создадим небольшой bash-скрипт deploy.sh, для деплоя на сервер без лишних телодвижений:
#!/bin/bash
pip3 install -r requirements.txt
python3 create_db.py
echo "END"
Здесь мы устанавливаем зависимости и создаем БД.
Если вы хотите запустить сайт — можете ввести две команды (на выбор):
- Тестовый сервер — python3 app.py.
- Продакшен сервер — gunicorn main:app --timeout 60.
И вот, все готово. Мы огромные молодцы. Можно скинуть свой сайт другу… Стоп, так мы же на локалхосте?
Если вы хотите опубликовать наше приложение на сервер, то есть два варианта:
- 1 — покупать сервер, самому все настроить и обслуживать.
- 2 — создать сервер на платформе netlify или похожей
Оба варианта нам не подойдут. Первый слишком муторный, а 2, к сожалению, из РФ недоступен.
Но есть еще один вариант — Cloud Apps от Timeweb Cloud. Это сервис для быстрого деплоя приложения, чтобы можно было его быстро опубликовать и забыть.
Думаю у многих возникала усталость от бесконечного конфигурирования серверов или постоянной монотонной рутины — подключился на сервер, клонировал репозиторий, установил, отключился. И так по кругу.
Или просто появлялась банальная лень — хотелось бы просто указать репозиторий, указать команды для сборки, и чтобы все сделали за тебя… Желательно чтобы и недорого, и с логами, и с поддержкой нескольких языков программирования и фреймворков. И Docker-контейнеры, и бекенд, и фронтенд.
Есть Netlify, Vercel — но их использование, из-за геополитического конфликта, в России ограничено.
Давайте опубликуем наше веб-приложение!
Приложение делится на три типа — frontend, backend и docker. Cloud Apps поддерживает большинство популярных языков программирования и фреймворков — «большая тройка» JS-библиотек (vue, react, angular) и другие популярные библиотеки или даже просто ванильный nodeJS, бекенд — от PHP и NodeJS до Java, Python, Go, Elixir и .NET, а также просто через Docker-контейнер. Мы будем использовать backend, python (Flask).
Плюсы Cloud Apps:
- Доступ к логам.
- Автоматическая установка зависимостей.
- Возможность выбора версий фреймворков и библиотек.
- Поддержка различных приложений — от простых докер-контейнеров до развертывания бекенда.
- Поддержка различных языков программирования и их фреймворков.
- Возможность использовать приватные репозитории.
- Автодеплой.
- Быстрота и легкость использования.
- Экономия затрат.
- Масштабируемость.
Минусы, это и так понятно, что для больших высоконагруженных проектов, где надо постоянно заходить на сервер, это будет не удобно.
❯ Как работает Cloud Apps?
Как я уже говорил, Apps — это облачный сервис для автоматической выгрузки кода и автодеплоя ваших приложений на серверах Timeweb.
Работает он так:
- Шаг 1. Вы заказываете сервис — подключаете репозиторий на GitHub, GitLab или Bitbucket и выбираете нужный фреймворк и сервер с подходящими параметрами.
- Шаг 2. Все остальное делает сервис: запускает сервер с необходимым ПО, «подтягивает» ваш код из репозитория, ставит зависимости, проверяет код и запускает его.
После запуска сервиса вы можете работать с кодом, как обычно: вносить правки и дополнения и делать коммиты в репозиторий. Сервис Apps автоматически отследит наличие изменений и, если у вас включен автодеплой, выкатит обновления в продакшен-среду.
Если что-то пошло не так и нужно откатиться на прошлую версию — запустите новый деплой с коммитом, по которому был последний успешный деплой.
К приложению будет привязан бесплатный технический домен с SSL Let's Encrypt, который можно использовать для тестирования и запросов к вашему приложению.
Основная функция сервиса приложений — автоматический деплой. Apps автоматически выгружает на сервер код вашего сайта, API-сервиса, приложения и т.п.
Для бекенда также автоматически запускается сервер на nginx, а также приложение хранится в Docker-контейнере.
Работа сервиса с frontend-приложениями имеет одно важное отличие от backend-приложений — после сборки не создается Docker-контейнер, приложение хранится в директории на сервере. Такое приложение — это статические файлы, которые отдаются клиентам с сервера.
Однако, в отличие от обычного размещения приложения на сервере, где вам нужно самостоятельно настраивать окружение, сервис Apps, как и в случае с бэкенд-приложениями, сделает всё за вас:
- «Подтянет» код из репозитория;
- Установит зависимости и ПО;
- Настроит Nginx;
- Выпустит SSL-сертификат;
- Выполнит сборку вашего приложения.
А в дальнейшем будет автоматически деплоить изменения — если вы оставите включенной опцию автодеплоя.
❯ Запуск приложения на Cloud Apps
Время попробовать задеплоить нашу открытую стену. Но перед созданием Cloud App нам требуется сам репозиторий — и этот репозиторий можеть быть на вашем GitHub (но также поддерживается GitLab и BitBucket) аккаунте, либо можно даже просто склонировать по URL. Мы советуем первый способ, т.к. так можно сделать всю настройку, и запушить все в приватный репозиторий, ведь при первом способе можно импортировать даже их, в отличии от второго.
Если у вас остались вопросы, то советуем перейти на документацию по подключению репозиториев. Там все рассказано подробно, если у вас возникнут ошибки или проблемы.
Для начала вам потребуется зайти на Timeweb Cloud и зарегистрироваться или войти в аккаунт.
После этого вы попадете в ваш личный кабинет:
После перейдите на вкладку «Apps»:
Займемся переносом приложения на Cloud Apps. Укажите URL вашего GIT-репозитория, в этом примере — наш сайт:
После этого в команде сборки указываем bash deploy.sh, а в команде запуска указываем gunicorn main:app --timeout 60:
Готово! Можете перейти на сайт и протестировать нашу небольшую публичную стену!
❯ Заключение
Приложения могут быть невероятно полезны. Быстро опубликовать сайт-визитку, протестировать что-то или сделать временный сайт — без проблем. Но когда дело касается серьезных проектов — лучше использовать обычные сервера и потратить время на ручную настройку.
Ссылки
Я надеюсь, вам понравилась эта статья. Мы смогли написать довольно хорошее приложение на Flask и быстро опубликовать его. Буквально за день вы получаете +1 проект в ваше портфолио.
Если у вас есть мнение по коду — то прошу их оставить в комментарии, я обязательно отвечу.
Дуров, верни стену!