
Привет, Хабр! Сегодня поговорим об одном интересном микро-фреймворке для 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 проект в ваше портфолио.
Если у вас есть мнение по коду — то прошу их оставить в комментарии, я обязательно отвечу.
Дуров, верни стену!

