ГлавнаяБлогРазворачиваем Flask-приложение на DigitalOcean App Platform
Алгоритмы

Разворачиваем Flask-приложение на DigitalOcean App Platform

Пошаговое руководство по развертыванию Flask-приложения на DigitalOcean App Platform с хранением изображений в Spaces. Научитесь деплоить на git push.

Al
Редакция Algolitalgolit.ru
12 мин чтения22 июня 2026 г.

Зачем это читать?

Вы когда-нибудь тратили вечер на настройку сервера для крошечного пет-проекта? Я покажу, как развернуть настоящее веб-приложение с загрузкой изображений на DigitalOcean App Platform + Spaces за 20 минут. Никакого Docker, никаких IAM-ролей — только git push и готово. В конце вы получите работающий код и избежите подводного камня, который стоил мне 20 минут жизни.

Что мы строим?

Одностраничное приложение на Flask: кнопка загрузки и сетка изображений. Вы выбираете файл, он попадает в Spaces (S3-совместимое объектное хранилище DigitalOcean), а сетка показывает все загруженные картинки. Просто, но затрагивает две ключевые вещи при выборе платформы: работает ли код без присмотра и куда деваются пользовательские файлы.

Вам понадобится аккаунт DigitalOcean (новые получают пробный кредит), GitHub и Python 3.10+.

Почему App Platform?

В ноябре 2022 года Heroku убил бесплатный тариф. Десятилетие Heroku был ответом на вопрос «куда задеплоить пет-проект» — git push heroku main и готово. Когда бесплатные dynos исчезли, поколение разработчиков внезапно столкнулось с выбором: либо возвращаться к сырым VPS (владеть сервером, патчами, файрволом), либо лезть в полное облако (где для хобби-приложения нужны IAM-роли и YAML-файл длиннее самого приложения).

App Platform — это ответ DigitalOcean на этот разрыв. Вы подключаете репозиторий, платформа определяет язык, собирает и запускает приложение. Никакого Dockerfile, никакого кластера. Цены плоские и понятные — вы знаете счёт до начала месяца. В паре со Spaces история с хранением так же скучна в хорошем смысле: Spaces совместим с S3, никаких проприетарных SDK. Если вы когда-либо трогали AWS S3, вы уже знаете API. Лучшая инфраструктура — та, о которой вы перестаёте думать.

Приложение в одном файле

Всё живёт в app.py. Конфигурация из переменных окружения — никаких секретов в репозитории:

import os
import uuid
import boto3
from botocore.client import Config
from flask import Flask, redirect, render_template, request, url_for

SPACES_KEY = os.environ.get("SPACES_KEY")
SPACES_SECRET = os.environ.get("SPACES_SECRET")
SPACES_REGION = os.environ.get("SPACES_REGION", "nyc3")
SPACES_BUCKET = os.environ.get("SPACES_BUCKET")
SPACES_ENDPOINT = os.environ.get("SPACES_ENDPOINT", f"https://{SPACES_REGION}.digitaloceanspaces.com")

ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg", "gif", "webp"}

app = Flask(__name__)
app.config["MAX_CONTENT_LENGTH"] = 8 * 1024 * 1024  # 8 МБ

Вот что меня удивило в первый раз: я использую boto3, AWS SDK, для общения с DigitalOcean. Spaces говорит на S3 API, так что все ваши существующие знания и инструменты переносятся. Единственное изменение — куда указывать:

def get_client():
    session = boto3.session.Session()
    return session.client(
        "s3",
        region_name=SPACES_REGION,
        endpoint_url=SPACES_ENDPOINT,
        aws_access_key_id=SPACES_KEY,
        aws_secret_access_key=SPACES_SECRET,
        config=Config(signature_version="s3v4"),
    )

Подводный камень, который стоил мне 20 минут

Видите SPACES_ENDPOINT? Это https://<region>.digitaloceanspaces.com — endpoint региона, без имени бакета. Когда вы создаёте бакет, DigitalOcean показывает URL вида https://my-bucket.atl1.digitaloceanspaces.com с бакетом в начале. Естественное желание — вставить его в код. Не делайте этого. boto3 ожидает endpoint региона и сам добавляет бакет. Если дать URL с бакетом, получите криптические ошибки подписи, похожие на проблемы с аутентификацией. Запомните это — остальное легко.

Остальное — обвязка: список объектов в бакете и построение публичных URL:

def public_url(key):
    return f"{SPACES_ENDPOINT.rstrip('/')}/{SPACES_BUCKET}/{key}"

def list_images(limit=60):
    client = get_client()
    response = client.list_objects_v2(Bucket=SPACES_BUCKET, Prefix="uploads/")
    objects = response.get("Contents", [])
    objects.sort(key=lambda o: o["LastModified"], reverse=True)
    return [public_url(o["Key"]) for o in objects[:limit]]

И маршруты — галерея, обработчик загрузки и health check для App Platform:

@app.route("/")
def index():
    images = list_images() if all([SPACES_KEY, SPACES_SECRET, SPACES_BUCKET]) else []
    return render_template("index.html", images=images)

@app.route("/upload", methods=["POST"])
def upload():
    file = request.files.get("image")
    if not file or "." not in file.filename:
        return redirect(url_for("index"))
    ext = file.filename.rsplit(".", 1)[1].lower()
    if ext not in ALLOWED_EXTENSIONS:
        return redirect(url_for("index"))
    key = f"uploads/{uuid.uuid4().hex}.{ext}"
    client = get_client()
    client.put_object(
        Bucket=SPACES_BUCKET,
        Key=key,
        Body=file,
        ACL="public-read",  # это делает изображение доступным в браузере
        ContentType=file.mimetype,
    )
    return redirect(url_for("index"))

@app.route("/health")
def health():
    return {"status": "ok"}, 200

Строка ACL="public-read" важна: бакеты по умолчанию приватные, так что без неё загрузка проходит, но изображения отдают 404. Если хотите публичными только некоторые файлы, используйте этот рычаг.

HTML-шаблон и немного CSS — в репозитории, они неинтересны для вставки.

Настройка Spaces

В панели DigitalOcean: Spaces Object Storage → Create a Spaces Bucket. Выберите регион (я использовал Атланту, atl1), дайте уникальное имя, создайте. Затем часть, которую пропускают: нужны Spaces keys, которые не совпадают с API-токенами DigitalOcean. Перейдите в API → Spaces Keys → Generate New Key. Вы получите access key и secret — secret показывается один раз. Скопируйте сейчас или генерируйте заново; кнопки «показать ещё раз» нет.

Локальный запуск

Всегда проверяйте на своей машине, прежде чем винить облако:

export SPACES_KEY="..."
export SPACES_SECRET="..."
export SPACES_REGION="atl1"
export SPACES_BUCKET="your-bucket"
pip install -r requirements.txt
python app.py

Откройте http://localhost:8080, загрузите что-нибудь. Если изображение появилось в сетке, ключи и бакет в порядке, облачная часть практически бесплатна.

Деплой — это та часть, которая продаёт

Запушьте на GitHub, затем в панели: App Platform → Create App → укажите ваш репозиторий. Теперь момент, который меня впечатлил: App Platform смотрит репозиторий, видит Python и просто собирает приложение. Никакого Dockerfile. Никакой конфигурации сборки. Команда запуска берётся из однострочного Procfile:

web: gunicorn --worker-tmp-dir /dev/shm --bind 0.0.0.0:$PORT app:app

Перед деплоем добавьте четыре переменные окружения в настройках компонента:

  • SPACES_KEY: ваш access key
  • SPACES_SECRET: ваш secret key
  • SPACES_REGION: atl1
  • SPACES_BUCKET: имя вашего бакета

Отметьте encrypt для ключа и секрета, чтобы они хранились как секреты, а не в открытом виде. Задеплойте. Вы получите URL вида *.ondigitalocean.app, и с этого момента каждый пуш в main будет автоматически передеплоиваться. Этот цикл — пуш, отойти, готово — и есть главная фишка.

Так это для вас?

Моя честная оценка после сборки: используйте App Platform + Spaces, если хотите запустить веб-приложение без мыслей о серверах, любите git push как кнопку деплоя и не хотите писать манифесты Kubernetes для пет-проектов. Совместимость Spaces с S3 означает нулевое изучение нового API для хранения.

Ищите другое, если нужен тонкий контроль над инфраструктурой, экзотические рантаймы или вы уже глубоко в экосистеме другого облака и миграция не стоит того.

Для пет-проектов, демо, внутренних инструментов и случаев «мне нужно, чтобы это было онлайн сегодня» — это соотношение усилий к работающему приложению трудно превзойти.

Если хотите развить пример: добавьте миниатюры через Pillow, CDN от Spaces для скорости или Managed Database для метаданных загрузок.

Весь проект открыт под лицензией MIT, и я искренне жду вкладов. В репозитории есть несколько good first issues — форкните, создайте PR, никакого разрешения не нужно.

Код, issues и полный шаблон: github.com/oceanforge/spaces-gallery. Если соберёте что-то с его помощью, буду рад увидеть.

Предупреждение: работающее приложение + бакет стоят несколько долларов в месяц. Если вы запустили это просто для пробы, удалите приложение, бакет и ключ Spaces, когда закончите.

Практический вывод

Прямо сейчас: зарегистрируйтесь на DigitalOcean, создайте бакет Spaces, сгенерируйте ключи, склонируйте репозиторий, запустите локально, затем запушьте на GitHub и задеплойте через App Platform. Весь процесс займёт меньше часа, и у вас будет работающее приложение с загрузкой изображений. Попробуйте — вы удивитесь, как это просто.

#Flask#DigitalOcean#App Platform#Spaces#деплой
Al
Редакция Algolit

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

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →