Фантастически быстрый деплой веб-приложения

image

Привет, Хабр! Сегодня поговорим об одном интересном микро-фреймворке для 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:




	
	
	Document


	{% block content %}
	{% endblock %}














Здесь вы можете увидеть фигурные скобки с процентами — они используются если нужны какие-то функции, например цикл 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'] = 'fc186fd3-3ace-46c8-8031-a819ec7d9a0e'
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 — это базовый шаблон, от него наследуются уже все остальные. В нем мы задаем форму и создаем блоки. В общем, вот как выглядит он у меня:




	
	
	Open Wall
	


	
	
{% block content %} {% endblock %}


Думаю, вы знаете 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 %}
			
	{% endfor %}
{% endblock %}


  • Командой extends 'base.html' мы говорим приложению наследовать наш базовый шаблон.
  • После мы редактируем блок content — добавляем в него список постов.
  • Дальше, в самом блоке контента, мы создаем цикл, и проходимся по всем элементам в списке posts (который мы передали в функции) и выводим: заголовок, контент, ссылку на профиль автора (рассмотрим позже) и дату создания поста.
  • В конце мы обозначаем, что завершили цикл и блок.


_kmqdr-mbz_bgsr1aott1a8hlbi.png

Теперь займемся функцией регистрации:

@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 %}
	

Регистрация

{% for msg in get_flashed_messages() %}
{{ msg }}
{% endfor %} Введите имя пользователя:
Введите пароль:
Повторите пароль:


{% endblock %}


В это шаблоне мы также используем функцию get_flashed_messages для отображения flash-сообщений.

z3i4gse59ginfke1vxxufeawvau.png

cp4txasrclnjxcq21zn75wpdfko.png

Следующим шагом мы создадим функцию логина пользователя:

@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 %}
	

Вход в аккаунт

{% for msg in get_flashed_messages() %}
{{ msg }}
{% endfor %} Введите имя пользователя:
Введите пароль:


{% endblock %}


Все практически также, как и при регистрации. Разве что без повторного ввода пароля.

aumxkrznz4nfouuhpkfhbwbb9oe.png

Теперь давайте немного отдохнем, напишем несложную функцию отображения профиля:

@app.route('/profile/')
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 %}
	

Пользователь: {{ username }}

{% if current_user.username == username %}
Выйти из аккаунта {% endif %}

Посты

{% for post in posts %} {% if post.author == username %}

{{ post.title }}

{{ post.content }}
Дата: {{ post.created_on }}
{% endif %} {% endfor %} {% endblock %}


Здесь я добавил небольшую фичу — если пользователь перешел на страницу своего же аккаунта, то мы добавляем ссылку для выхода из профиля.

n5qwamqa9ljjt6bztcata24bwgs.png

apb39oad8s86lvfbwhws9p4ibrc.png

И наконец-то последняя функция — функция создания нового поста.

@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 %}
	

Создание статьи

{% for msg in get_flashed_messages() %}
{{ msg }}
{% endfor %} Введите заголовок статьи
Введите текст статьи

{% endblock %}


--ckwkpbblqprtujluuiy-akp4s.png

Финальные приготовления


В файл 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 и зарегистрироваться или войти в аккаунт.

w1sqd3ss6nyzmez79agreuokyuc.png

После этого вы попадете в ваш личный кабинет:

ql2eaoxk5mruuc6oamwooxsdv5a.png

После перейдите на вкладку «Apps»:

apbkgfd6emnrwwajqmsm6bsra-g.png

Займемся переносом приложения на Cloud Apps. Укажите URL вашего GIT-репозитория, в этом примере — наш сайт:

bgxytxuxv2mswcpp7jxw0m8nfdi.png

После этого в команде сборки указываем bash deploy.sh, а в команде запуска указываем gunicorn main: app --timeout 60:

aoq3pc0_unx9cq2mlouhiukydxk.png

Готово! Можете перейти на сайт и протестировать нашу небольшую публичную стену!

_ut6b7jmdsubxljxrpmbpwvcat4.png

Заключение


Приложения могут быть невероятно полезны. Быстро опубликовать сайт-визитку, протестировать что-то или сделать временный сайт — без проблем. Но когда дело касается серьезных проектов — лучше использовать обычные сервера и потратить время на ручную настройку.

Ссылки


Я надеюсь, вам понравилась эта статья. Мы смогли написать довольно хорошее приложение на Flask и быстро опубликовать его. Буквально за день вы получаете +1 проект в ваше портфолио.

Если у вас есть мнение по коду — то прошу их оставить в комментарии, я обязательно отвечу.

Дуров, верни стену!



Возможно, захочется почитать и это:

Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале


b5pjofdoxth14ro-rjsrn7sbmiy.png

© Habrahabr.ru