docs: rewrite README in plain human wording
This commit is contained in:
parent
a79f40cec3
commit
452ae5b898
1 changed files with 114 additions and 198 deletions
302
README.md
302
README.md
|
|
@ -1,89 +1,61 @@
|
|||
# Как запустить свой проект на платформе
|
||||
# Запуск проекта на платформе
|
||||
|
||||
Один файл `.env` + один пуш → сайт на твоём домене с автоматическим SSL.
|
||||
Это единственный репозиторий, который нужно знать: тут и гайд, и файлы стартера,
|
||||
и сам платформенный action.
|
||||
Кладёшь в репу `.env` с доменом, пушишь — платформа собирает проект и поднимает
|
||||
сайт с SSL. Форкать этот репо не нужно: он тут как справка и как источник двух
|
||||
файлов, которые копируешь к себе.
|
||||
|
||||
---
|
||||
## Быстрый старт
|
||||
|
||||
## TL;DR — 4 шага
|
||||
|
||||
1. Скопируй в **свой** репозиторий 2 файла из папки [`starter/`](starter) этого репо:
|
||||
- `.forgejo/workflows/deploy.yml` — **не трогать** (тянет платформенный action по ссылке)
|
||||
1. Скопируй в свой репозиторий два файла из [`starter/`](starter):
|
||||
- `.forgejo/workflows/deploy.yml` — как есть, не редактируй
|
||||
- `.env` — из `starter/.env.example`
|
||||
2. В `.env` укажи домен: `DOMAIN=mysite.com`
|
||||
3. Пропиши A-запись домена на `51.161.128.94`
|
||||
4. Запушь в основную ветку. Через минуту-полторы сайт открыт по HTTPS.
|
||||
|
||||
Форкать/клонировать этот репо **не надо** — деплой цепляется к твоей репе по
|
||||
самому факту наличия `.forgejo/workflows/deploy.yml`.
|
||||
2. Заполни `.env`, минимум:
|
||||
```env
|
||||
DOMAIN=mysite.com
|
||||
```
|
||||
3. Пропиши **A-запись** домена → IP платформы **`51.161.128.94`**.
|
||||
4. `git push` в дефолтную ветку. Через ~30–90 сек сайт открыт на `https://mysite.com`.
|
||||
Дальше каждый push пересобирает и передеплоивает. `deploy.yml` больше не трогаешь.
|
||||
|
||||
Дальше любой push = редеплой. `deploy.yml` трогать не нужно никогда.
|
||||
## Что происходит при пуше
|
||||
|
||||
---
|
||||
Workflow дёргает платформенный action (`action.yml` из этого репо): он клонирует
|
||||
твою репу, читает `.env`, определяет тип сборки, собирает и публикует, прописывает
|
||||
домен в Caddy. Caddy сам выпускает сертификат Let's Encrypt.
|
||||
|
||||
## Как это работает (флоу)
|
||||
## Типы проектов
|
||||
|
||||
```
|
||||
git push
|
||||
│
|
||||
▼
|
||||
Forgejo Actions запускает .forgejo/workflows/deploy.yml
|
||||
│ (uses: https://git.extract.team/platform/launch@main)
|
||||
▼
|
||||
platform/launch action:
|
||||
├─ клонирует твою репу
|
||||
├─ читает .env → DOMAIN, тип деплоя (static / docker / docker-db)
|
||||
├─ билд: static → bun build | docker → docker build
|
||||
├─ публикует статику в /srv/sites │ или поднимает контейнер
|
||||
└─ пишет фрагмент Caddy на твой домен
|
||||
▼
|
||||
Caddy автоматически выпускает SSL (Let's Encrypt) → https://mysite.com живой
|
||||
```
|
||||
Тип берётся из `DEPLOY` в `.env`, либо определяется сам: есть `Dockerfile` — docker,
|
||||
нет — static.
|
||||
|
||||
Один репозиторий = один сайт (один `.env`, один `DOMAIN`).
|
||||
### static
|
||||
|
||||
---
|
||||
|
||||
## Тип проекта: static / docker / docker-db
|
||||
|
||||
Тип определяется полем `DEPLOY` в `.env` или авто-детектом (по наличию `Dockerfile`).
|
||||
|
||||
### 1. `static` — фронт (90% проектов)
|
||||
|
||||
Vite / CRA / Next static export. `bun build` → статика раздаётся напрямую, **без
|
||||
контейнера**. Деплой за секунды; редеплой = только заливка новых файлов.
|
||||
Фронт на Vite / CRA / Next export. Собирается через bun, раздаётся как статика, без
|
||||
контейнера — самый быстрый вариант.
|
||||
|
||||
```env
|
||||
DOMAIN=mysite.com
|
||||
# DEPLOY=static # дефолт, если в корне НЕТ Dockerfile
|
||||
# BUILD_DIR=dist # Vite=dist (дефолт), CRA=build, Next export=out
|
||||
# BUILD_DIR=dist # Vite=dist, CRA=build, Next export=out
|
||||
```
|
||||
|
||||
- **Vite + React** — ничего настраивать, дефолт.
|
||||
- **CRA** — `BUILD_DIR=build`.
|
||||
- **Next.js static export** — в `next.config`: `output: 'export'`,
|
||||
`images: { unoptimized: true }`, `trailingSlash: true`; в `.env` — `BUILD_DIR=out`.
|
||||
SSR / API routes / Server Actions в static-режиме не работают.
|
||||
Для Next.js в static-режиме нужно в `next.config`: `output: 'export'`,
|
||||
`images.unoptimized: true`, `trailingSlash: true`, и `BUILD_DIR=out`. SSR и API
|
||||
routes в этом режиме не работают.
|
||||
|
||||
### 2. `docker` — фронт + бэкенд в контейнере
|
||||
### docker
|
||||
|
||||
Положи `Dockerfile` в корень. Платформа собирает образ, поднимает контейнер и
|
||||
проксирует домен на него. Контейнер должен слушать порт из `APP_PORT`.
|
||||
Свой `Dockerfile` в корне. Платформа собирает образ, поднимает контейнер и
|
||||
проксирует на него домен. Контейнер должен слушать `APP_PORT`.
|
||||
|
||||
```env
|
||||
DOMAIN=mysite.com
|
||||
DEPLOY=docker # или просто наличие Dockerfile
|
||||
APP_PORT=3000 # порт контейнера (default 80)
|
||||
DEPLOY=docker
|
||||
APP_PORT=3000
|
||||
```
|
||||
|
||||
### 3. `docker-db` — docker + персистентная база (SQLite)
|
||||
### docker-db
|
||||
|
||||
Как `docker`, плюс платформа монтирует named-volume в `/data` и прокидывает
|
||||
`DATABASE_URL=file:/data/app.db`. Том переживает редеплои.
|
||||
То же, что docker, плюс SQLite: том монтируется в `/data`, в контейнер прокидывается
|
||||
`DATABASE_URL=file:/data/app.db`. Том переживает редеплои. Миграции гоняешь сам в
|
||||
entrypoint. `DATABASE_URL` в `RUNTIME_KEYS` добавлять не надо — она ставится сама.
|
||||
|
||||
```env
|
||||
DOMAIN=mysite.com
|
||||
|
|
@ -91,193 +63,137 @@ DEPLOY=docker-db
|
|||
APP_PORT=3000
|
||||
```
|
||||
|
||||
В коде читаешь `process.env.DATABASE_URL`. Миграции (drizzle/prisma) запускай в
|
||||
своём entrypoint — платформа их не делает. **Не** указывай `DATABASE_URL` в
|
||||
`RUNTIME_KEYS` (ставится автоматически).
|
||||
|
||||
---
|
||||
|
||||
## Домен и SSL
|
||||
|
||||
- **`DOMAIN`** — обязательное поле. Заранее пропиши **A-запись** домена на IP
|
||||
платформы `51.161.128.94` (у своего регистратора).
|
||||
- **`www.`** подключается автоматически, если у `www.<domain>` тоже есть A-запись на
|
||||
наш IP. Не нужен — поставь `WWW=false`.
|
||||
- **SSL выпускается сам** (Let's Encrypt), обычно за 30–60 сек после первого деплоя,
|
||||
когда DNS уже указывает на наш IP. Отдельный серт на каждый домен. Ничего
|
||||
настраивать не надо.
|
||||
- **Не открывай сайт в браузере, пока workflow не зелёный** — пока идёт деплой,
|
||||
валидного серта ещё может не быть, и браузер закеширует «сломанный SSL». Дождись
|
||||
зелёного run в **Actions** (или открой в инкогнито).
|
||||
- `DOMAIN` обязателен. Заранее пропиши A-запись домена на `51.161.128.94`.
|
||||
- `www` подключается сам, если у него тоже есть A-запись на наш IP. Не нужен —
|
||||
поставь `WWW=false`.
|
||||
- Сертификат выпускается автоматически, обычно за полминуты после первого деплоя.
|
||||
На каждый домен — отдельный.
|
||||
- Пока workflow не позеленел, не открывай сайт в обычном браузере: сертификата ещё
|
||||
может не быть, и браузер закеширует ошибку. Дождись зелёного run или открой в
|
||||
инкогнито.
|
||||
|
||||
### Доп. домены — один сайт на нескольких доменах
|
||||
Один сайт на нескольких доменах:
|
||||
|
||||
```env
|
||||
DOMAIN=mysite.com
|
||||
EXTRA_DOMAINS=mysite.io,app.mysite.com,promo.xyz
|
||||
EXTRA_DOMAINS=mysite.io,app.mysite.com
|
||||
```
|
||||
|
||||
Тот же сайт на всех доменах, на **каждый** — свой авто-SSL. От тебя: A-запись
|
||||
каждого домена на наш IP. `www.` для доп-доменов сам не добавляется — если нужен,
|
||||
впиши `www.mysite.io` явным пунктом.
|
||||
На каждый домен — свой сертификат, для каждого пропиши A-запись. `www` для
|
||||
доп-доменов сам не добавляется (нужен — впиши `www.mysite.io` отдельным пунктом).
|
||||
|
||||
---
|
||||
## Twitter
|
||||
|
||||
## Twitter / X проекта
|
||||
|
||||
Хочешь, чтобы в **админ-панели** рядом с сайтом показывался его твиттер — добавь в
|
||||
`.env`:
|
||||
Чтобы в админке рядом с сайтом показывался твиттер проекта:
|
||||
|
||||
```env
|
||||
TWITTER_URL=https://x.com/mysite
|
||||
```
|
||||
|
||||
Принимается полный URL, `@handle` или голый `handle` (`TWITTER=` тоже работает).
|
||||
Платформа нормализует и покажет как `𝕏 @mysite`. Ни на деплой, ни на сайт не влияет —
|
||||
только метаданные для админки.
|
||||
|
||||
---
|
||||
Принимается URL, `@handle` или просто `handle`. На сам сайт не влияет — только метка
|
||||
в панели.
|
||||
|
||||
## Монорепозиторий
|
||||
|
||||
Платформа деплоит **один сайт на репозиторий** (один `.env`, один `DOMAIN`). Отдельного
|
||||
поля «папка приложения» нет — сборка идёт от **корня репы**. Поэтому для монорепо:
|
||||
Один репозиторий = один сайт. Отдельного поля под папку приложения нет — сборка идёт
|
||||
от корня репы. Поэтому:
|
||||
|
||||
- `.env` и `package.json` (или `Dockerfile`) — **в корне** репозитория.
|
||||
- **static:** корневой скрипт `build` должен собирать нужное приложение (через
|
||||
workspaces / turbo / nx), а `BUILD_DIR` — указывать на его вывод:
|
||||
- `.env` и `package.json` (или `Dockerfile`) держи в корне.
|
||||
- static: корневой скрипт `build` должен собирать нужное приложение, а `BUILD_DIR` —
|
||||
указывать на его вывод:
|
||||
```env
|
||||
DOMAIN=mysite.com
|
||||
BUILD_DIR=apps/web/dist # куда собирается целевое приложение
|
||||
BUILD_DIR=apps/web/dist
|
||||
```
|
||||
```jsonc
|
||||
// package.json в корне
|
||||
```json
|
||||
{ "scripts": { "build": "npm run build -w apps/web" } }
|
||||
```
|
||||
- **docker:** `Dockerfile` в корне, контекст сборки = весь репозиторий, так что он
|
||||
спокойно `COPY apps/web …` и собирает нужный пакет. Задай `APP_PORT`.
|
||||
- Нужно задеплоить **несколько** приложений из одного монорепо на **разные** домены —
|
||||
сделай под каждое отдельный репозиторий (один репо = один сайт).
|
||||
- docker: `Dockerfile` в корне, контекст сборки — весь репозиторий, так что `COPY`
|
||||
нужного пакета работает.
|
||||
- Нужно несколько приложений на разные домены — заведи под каждое отдельный репозиторий.
|
||||
|
||||
---
|
||||
## Установочный сабдомен (`curl get.домен | sh`)
|
||||
|
||||
## Установочный сабдомен `curl get.<domain> | sh` (self-service)
|
||||
Только для static, настраивается без админа:
|
||||
|
||||
Красивый one-liner установки — делается **сам, без админа**, для `static`-сайтов:
|
||||
1. Положи скрипт в `public/install.sh` (папка `public/` у Vite попадает в корень сборки).
|
||||
2. Пропиши A-запись `get` → наш IP.
|
||||
3. Добавь `INSTALL_SH=install.sh` в `.env` и запушь.
|
||||
|
||||
1. Положи скрипт в репу → `public/install.sh` (папка `public/` у Vite попадает в
|
||||
`dist/` как есть → окажется в корне сайта).
|
||||
2. **DNS:** A-запись `get` → IP платформы (host = `get`).
|
||||
3. В `.env`: `INSTALL_SH=install.sh` → `git push`.
|
||||
Платформа поднимет `get.домен`, выпустит SSL и будет отдавать этот файл как
|
||||
`text/plain`. Убрал `INSTALL_SH` — сабдомен снимется на следующем деплое.
|
||||
|
||||
CI сам поднимет `get.<domain>`, выпустит SSL и будет отдавать `install.sh` как
|
||||
`text/plain`. Убери `INSTALL_SH` и запушь — сабдомен снимется. В админке этот
|
||||
сабдомен виден в строке «сабдомены».
|
||||
## Секреты
|
||||
|
||||
---
|
||||
`.env` в контейнер не попадает — он читается только при деплое. Секреты заводишь в
|
||||
Forgejo: репа → Settings → Actions → Secrets, имя секрета = имя переменной. Потом
|
||||
перечисляешь нужные:
|
||||
|
||||
## Секреты (DATABASE_URL, API-ключи)
|
||||
|
||||
`.env` в runtime контейнера **не попадает** (парсится только при деплое). Секреты:
|
||||
|
||||
1. **Forgejo → репа → Settings → Actions → Secrets → Add Secret.** Имя секрета = имя
|
||||
переменной окружения (`DATABASE_URL`, `JWT_SECRET`).
|
||||
2. В `.env` перечисли, какие прокинуть:
|
||||
```env
|
||||
RUNTIME_KEYS=DATABASE_URL,JWT_SECRET
|
||||
```
|
||||
3. В коде: `process.env.DATABASE_URL`.
|
||||
|
||||
Платформа берёт **только** ключи из `RUNTIME_KEYS`. (Только для `docker`/`docker-db`.)
|
||||
|
||||
---
|
||||
|
||||
## Скорость билда
|
||||
|
||||
Фронт собирается через **bun** (install в 10–30× быстрее npm). Закоммить lock:
|
||||
|
||||
```bash
|
||||
bun install # создаст bun.lock
|
||||
git add bun.lock && git commit -m "chore: bun lock" && git push
|
||||
```env
|
||||
RUNTIME_KEYS=DATABASE_URL,JWT_SECRET
|
||||
```
|
||||
|
||||
### docker-сайты — кеш слоёв и лёгкие образы
|
||||
В контейнер попадут только они. В коде читаешь как обычно — `process.env.DATABASE_URL`.
|
||||
|
||||
Сборка идёт с **BuildKit** и `--cache-from` предыдущего образа. Чтобы это работало:
|
||||
## Скорость сборки
|
||||
|
||||
- ставь зависимости ДО копирования кода (слой install кешируется):
|
||||
```dockerfile
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
```
|
||||
- cache-mount'ы:
|
||||
```dockerfile
|
||||
# syntax=docker/dockerfile:1.7
|
||||
RUN --mount=type=cache,target=/root/.npm npm ci
|
||||
RUN --mount=type=cache,target=/app/.next/cache npm run build
|
||||
```
|
||||
- **Next.js** → `output: 'standalone'` в `next.config` роняет образ с ~1–3 ГБ до
|
||||
~150–250 МБ → деплой заметно быстрее.
|
||||
Фронт собирается через bun — install в разы быстрее npm, закоммить `bun.lock`.
|
||||
|
||||
---
|
||||
docker-образы собираются с BuildKit и кешем от предыдущего образа. Ставь зависимости
|
||||
до копирования кода, чтобы слой install кешировался:
|
||||
|
||||
## Выключить сайт (kill-switch)
|
||||
```dockerfile
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
```
|
||||
|
||||
Для Next.js `output: 'standalone'` в `next.config` роняет образ с гигабайтов до
|
||||
~200 МБ — деплой заметно быстрее.
|
||||
|
||||
## Выключить сайт
|
||||
|
||||
```env
|
||||
ENABLED=false
|
||||
```
|
||||
|
||||
Push → контейнер/файлы удаляются, домен отдаёт 404. Убери строку (или `true`) +
|
||||
push → поднимается обратно. Том и секреты сохраняются. (Админ может выключить и без
|
||||
коммита — переменной `ENABLED=false` в Settings → Actions → Variables.)
|
||||
Push — контейнер и файлы удаляются, домен отдаёт 404. Убрал строку (или `true`) —
|
||||
вернулся. Том и секреты остаются. Админ может выключить и без коммита — переменной
|
||||
`ENABLED=false` в Settings → Actions → Variables.
|
||||
|
||||
---
|
||||
## Переменные `.env`
|
||||
|
||||
## Все переменные `.env`
|
||||
|
||||
| Key | Default | Зачем |
|
||||
|----------------|---------|-------|
|
||||
| `DOMAIN` | — | Обязательно (кроме `ENABLED=false`) |
|
||||
| Ключ | Дефолт | Зачем |
|
||||
|-----------------|---------|-------|
|
||||
| `DOMAIN` | — | Обязателен (кроме `ENABLED=false`) |
|
||||
| `DEPLOY` | auto | `static` / `docker` / `docker-db` |
|
||||
| `BUILD_DIR` | `dist` | static: папка сборки (в монорепо — путь до вывода приложения) |
|
||||
| `APP_PORT` | `80` | docker: порт контейнера |
|
||||
| `WWW` | `auto` | `false` — не подключать www |
|
||||
| `EXTRA_DOMAINS`| — | доп. домены через запятую (тот же сайт, свой SSL на каждый) |
|
||||
| `TWITTER_URL` | — | твиттер/X проекта для админки (`@handle` / URL) |
|
||||
| `INSTALL_SH` | — | static: отдать файл-скрипт на `get.<domain>` (`curl \| sh`) |
|
||||
| `RUNTIME_KEYS` | — | docker: список секретов в контейнер |
|
||||
| `EXTRA_DOMAINS` | — | доп. домены через запятую, свой SSL на каждый |
|
||||
| `TWITTER_URL` | — | твиттер/X для админки |
|
||||
| `INSTALL_SH` | — | static: отдать скрипт на `get.<domain>` |
|
||||
| `RUNTIME_KEYS` | — | docker: какие секреты прокинуть в контейнер |
|
||||
| `ENABLED` | `true` | `false` — выключить сайт |
|
||||
|
||||
---
|
||||
## Доступ к гиту
|
||||
|
||||
## Доступ к гиту (один раз)
|
||||
|
||||
- Форж: **https://git.extract.team**, логин — твой username.
|
||||
- SSH: `git@git.extract.team:2223`. Добавь ключ: **Settings → SSH/GPG Keys**, затем в
|
||||
`~/.ssh/config`:
|
||||
- Форж: https://git.extract.team, логин прежний.
|
||||
- SSH: `git@git.extract.team:2223`, ключ добавляешь в Settings → SSH/GPG Keys.
|
||||
```
|
||||
Host git.extract.team
|
||||
Port 2223
|
||||
User git
|
||||
```
|
||||
Ремоут: `git remote set-url origin git@git.extract.team:<логин>/<репа>.git`
|
||||
(или HTTPS — логин + пароль).
|
||||
|
||||
---
|
||||
## Если не работает
|
||||
|
||||
## Частые проблемы
|
||||
|
||||
- **502 / сайт не грузится сразу после пуша** — деплой ещё идёт или серт выпускается.
|
||||
Дождись зелёного run в Actions.
|
||||
- **502 сразу после пуша** — деплой ещё идёт или выпускается сертификат, дождись
|
||||
зелёного run.
|
||||
- **`build output 'dist' not found`** — не та `BUILD_DIR` (CRA → `build`, Next export
|
||||
→ `out`, монорепо → путь до вывода приложения).
|
||||
- **`no .env` / `no package.json`** — оба файла должны быть в **корне** репозитория.
|
||||
- **SSL не выпускается** — нет A-записи домена на `51.161.128.94` (в логе деплоя
|
||||
будет `::warning::`). Пропиши DNS — серт выпустится сам.
|
||||
- Застрял — пиши админу.
|
||||
|
||||
---
|
||||
|
||||
_Файлы для копирования — в [`starter/`](starter). Платформенный action — [`action.yml`](action.yml)._
|
||||
- **`no .env` / `no package.json`** — оба файла должны лежать в корне репы.
|
||||
- **Сертификат не выпускается** — нет A-записи домена на `51.161.128.94`.
|
||||
- Не разобрался — пиши админу.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue