Compare commits
3 Commits
08c6d62498
...
1e215ac3d2
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e215ac3d2 | |||
| 716196dbe0 | |||
| 84f6d7c31a |
@@ -6,7 +6,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
group: ci-${{ gitea.workflow }}-${{ gitea.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -15,27 +15,27 @@ concurrency:
|
|||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: git.hellomanoel.com
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ gitea.repository }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
name: publish-and-deploy-staging
|
name: publish-and-deploy-staging
|
||||||
if: >-
|
if: >-
|
||||||
github.event.workflow_run.conclusion == 'success' &&
|
gitea.event.workflow_run.conclusion == 'success' &&
|
||||||
github.event.workflow_run.event == 'push' &&
|
gitea.event.workflow_run.event == 'push' &&
|
||||||
github.event.workflow_run.head_branch == 'main'
|
gitea.event.workflow_run.head_branch == 'main'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout deployed SHA
|
- name: Checkout deployed SHA
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.workflow_run.head_sha }}
|
ref: ${{ gitea.event.workflow_run.head_sha }}
|
||||||
|
|
||||||
- name: Set image metadata
|
- name: Set image metadata
|
||||||
id: meta
|
id: meta
|
||||||
run: |
|
run: |
|
||||||
SHA="${{ github.event.workflow_run.head_sha }}"
|
SHA="${{ gitea.event.workflow_run.head_sha }}"
|
||||||
SHORT_SHA="${SHA:0:7}"
|
SHORT_SHA="${SHA:0:7}"
|
||||||
IMAGE="${REGISTRY}/${IMAGE_NAME}"
|
IMAGE="${REGISTRY}/${IMAGE_NAME}"
|
||||||
IMAGE="$(echo "$IMAGE" | tr '[:upper:]' '[:lower:]')"
|
IMAGE="$(echo "$IMAGE" | tr '[:upper:]' '[:lower:]')"
|
||||||
@@ -43,12 +43,14 @@ jobs:
|
|||||||
echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
|
echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
|
||||||
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"
|
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Log in to GHCR
|
- name: Log in to Gitea registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
# Gitea's GITEA_TOKEN cannot push OCI packages (gitea#23642); a PAT
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
# with read:package/write:package scopes is required instead.
|
||||||
|
username: ${{ secrets.GITEA_REGISTRY_USER }}
|
||||||
|
password: ${{ secrets.GITEA_PAT }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@@ -4,7 +4,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
sha:
|
sha:
|
||||||
description: Full git SHA already published to GHCR (same digest used by staging)
|
description: Full git SHA already published to the Gitea registry (same digest used by staging)
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
confirm:
|
confirm:
|
||||||
@@ -21,8 +21,8 @@ concurrency:
|
|||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: git.hellomanoel.com
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ gitea.repository }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
promote:
|
promote:
|
||||||
@@ -50,12 +50,14 @@ jobs:
|
|||||||
echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
|
echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
|
||||||
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"
|
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Log in to GHCR
|
- name: Log in to Gitea registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
# Gitea's GITEA_TOKEN cannot push OCI packages (gitea#23642); a PAT
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
# with read:package/write:package scopes is required instead.
|
||||||
|
username: ${{ secrets.GITEA_REGISTRY_USER }}
|
||||||
|
password: ${{ secrets.GITEA_PAT }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
11
CLAUDE.md
11
CLAUDE.md
@@ -22,13 +22,14 @@ Write for the problem at hand, not an imagined future. **DRY**: extract and reus
|
|||||||
|
|
||||||
PHP and Composer are **not on PATH** in this environment, and `vendor/` and `node_modules/` are absent. Every `composer …` / `php artisan …` command in `AGENTS.md` and `README.md` assumes a PHP 8.4+ runtime with Composer 2 installed. Verify the toolchain before promising a command ran.
|
PHP and Composer are **not on PATH** in this environment, and `vendor/` and `node_modules/` are absent. Every `composer …` / `php artisan …` command in `AGENTS.md` and `README.md` assumes a PHP 8.4+ runtime with Composer 2 installed. Verify the toolchain before promising a command ran.
|
||||||
|
|
||||||
## Git remote auth — two GitHub accounts
|
## Git remotes — Gitea origin, GitHub legacy
|
||||||
|
|
||||||
`origin` is `git@github.com:manoel-freitas/amore-site.git`, owned by the **`manoel-freitas`** account. The machine's default SSH identity is a different account (`manoel-freitas-neto`) that cannot see this repo, so pushes fail with `ERROR: Repository not found.` — an access error that reads like a missing repo.
|
`origin` is `git@git.hellomanoel.com:manoel-freitas/amare.git` (self-hosted Gitea). CI/CD and the container registry live there (`git.hellomanoel.com`). Verify SSH with `ssh -T git@git.hellomanoel.com` → should greet `Hi there, manoel-freitas!`.
|
||||||
|
|
||||||
- Correct key: `~/.ssh/id_github_pessoal`. Verify with `ssh -i ~/.ssh/id_github_pessoal -o IdentitiesOnly=yes -T git@github.com` → should greet `Hi manoel-freitas!`.
|
The remote named `github` is the legacy mirror `git@github.com:manoel-freitas/amore-site.git`. Push there only when intentionally syncing the backup. That GitHub account still needs `~/.ssh/id_github_pessoal` (or an equivalent key) when the machine's default identity is a different GitHub user (`manoel-freitas-neto`) that cannot see the repo.
|
||||||
- The repo has `core.sshCommand = ssh -i ~/.ssh/id_github_pessoal -o IdentitiesOnly=yes` set locally, so plain `git push` works. If that config is lost, restore it instead of editing the remote URL.
|
|
||||||
- **`gh` authenticates separately**, by token rather than SSH key. As of 2026-08-10 it is logged in as `manoel-freitas`, so `gh pr create` / `gh repo view` work. Confirm with `gh auth status` before assuming: if it reports `manoel-freitas-neto`, that account cannot see this repo and every `gh` call fails on it. Recovering needs an interactive `gh auth login` (or `gh auth switch` with both accounts added), so ask the user to run it.
|
- Prefer plain `git push` / `git push origin <branch>` against Gitea.
|
||||||
|
- **`gh`** talks to GitHub only. Use the Gitea web UI or API for PRs on `amare`. If you still need `gh` against the legacy remote, confirm `gh auth status` shows `manoel-freitas`.
|
||||||
|
|
||||||
## Request spine for the public site
|
## Request spine for the public site
|
||||||
|
|
||||||
|
|||||||
@@ -138,10 +138,10 @@ Após `php artisan db:seed`:
|
|||||||
- [docs/adr/](docs/adr/) — ADRs aceitas
|
- [docs/adr/](docs/adr/) — ADRs aceitas
|
||||||
- [docs/conventions/php-strict-types.md](docs/conventions/php-strict-types.md) — convenção de strict types
|
- [docs/conventions/php-strict-types.md](docs/conventions/php-strict-types.md) — convenção de strict types
|
||||||
- [docs/operations/atualizacao-de-conteudo.md](docs/operations/atualizacao-de-conteudo.md) — runbook de atualização de conteúdo do site pelo painel admin
|
- [docs/operations/atualizacao-de-conteudo.md](docs/operations/atualizacao-de-conteudo.md) — runbook de atualização de conteúdo do site pelo painel admin
|
||||||
- [docs/deployment/dokploy.md](docs/deployment/dokploy.md) — deploy staging/produção no Dokploy + GHCR
|
- [docs/deployment/dokploy.md](docs/deployment/dokploy.md) — deploy staging/produção no Dokploy + registry Gitea
|
||||||
|
|
||||||
## Deploy (Dokploy)
|
## Deploy (Dokploy)
|
||||||
|
|
||||||
Staging publica automaticamente após CI verde em `main` (imagem GHCR por SHA + alias `:staging`). Produção promove a **mesma digest** com workflow manual `Promote production` (sem rebuild).
|
Staging publica automaticamente após CI verde em `main` (imagem no registry Gitea por SHA + alias `:staging`). Produção promove a **mesma digest** com workflow manual `Promote production` (sem rebuild).
|
||||||
|
|
||||||
Ver runbook completo: [docs/deployment/dokploy.md](docs/deployment/dokploy.md).
|
Ver runbook completo: [docs/deployment/dokploy.md](docs/deployment/dokploy.md).
|
||||||
|
|||||||
4
SPEC.md
4
SPEC.md
@@ -2073,7 +2073,7 @@ quality → Pint check + PHPStan/Larastan + audits + testes
|
|||||||
### 14.3 Branches e ambientes
|
### 14.3 Branches e ambientes
|
||||||
|
|
||||||
- PR: testes e preview opcional;
|
- PR: testes e preview opcional;
|
||||||
- `main`: build imutável por SHA publicado no GHCR e deploy automático em staging via Dokploy;
|
- `main`: build imutável por SHA publicado no registry Gitea (`git.hellomanoel.com`) e deploy automático em staging via Dokploy;
|
||||||
- staging: Dokploy Compose executa migração, healthcheck `/up` e smoke pós-deploy (`/up`, `/`, `/admin/login`);
|
- staging: Dokploy Compose executa migração, healthcheck `/up` e smoke pós-deploy (`/up`, `/`, `/admin/login`);
|
||||||
- produção: promoção da mesma imagem aprovada, sem rebuild (retag do digest em `:production`);
|
- produção: promoção da mesma imagem aprovada, sem rebuild (retag do digest em `:production`);
|
||||||
- produção requer aprovação humana explícita no MVP (`workflow_dispatch` com confirmação);
|
- produção requer aprovação humana explícita no MVP (`workflow_dispatch` com confirmação);
|
||||||
@@ -2487,7 +2487,7 @@ Toda operação financeira deve:
|
|||||||
| ADR-011 | Cloudflare R2 (S3-compatible) como storage de objetos em produção | Aceita |
|
| ADR-011 | Cloudflare R2 (S3-compatible) como storage de objetos em produção | Aceita |
|
||||||
| ADR-012 | E-mail transacional via Resend (mailer nativo Laravel) | Aceita |
|
| ADR-012 | E-mail transacional via Resend (mailer nativo Laravel) | Aceita |
|
||||||
| ADR-013 | Design system Heritage Editorial para o site público | Aceita |
|
| ADR-013 | Design system Heritage Editorial para o site público | Aceita |
|
||||||
| ADR-014 | Deploy via Dokploy Compose com imagem imutável por SHA no GHCR | Aceita |
|
| ADR-014 | Deploy via Dokploy Compose com imagem imutável por SHA no registry Gitea | Aceita |
|
||||||
| ADR-015 | Site público permanece Blade + JS vanilla; Livewire e Alpine ficam restritos ao Filament até o gatilho de §22. Emenda o texto da ADR-002 | Aceita |
|
| ADR-015 | Site público permanece Blade + JS vanilla; Livewire e Alpine ficam restritos ao Filament até o gatilho de §22. Emenda o texto da ADR-002 | Aceita |
|
||||||
| ADR-016 | Lançamento de 31/08/2026 entrega apenas o site institucional (Fases 0–1); Fases 2–5 seguem especificadas e adiadas, sem data | Aceita |
|
| ADR-016 | Lançamento de 31/08/2026 entrega apenas o site institucional (Fases 0–1); Fases 2–5 seguem especificadas e adiadas, sem data | Aceita |
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Shared Compose for Dokploy staging and production.
|
# Shared Compose for Dokploy staging and production.
|
||||||
# Both stacks use the same file with different env:
|
# Both stacks use the same file with different env:
|
||||||
# APP_IMAGE=ghcr.io/<owner>/<repo>
|
# APP_IMAGE=git.hellomanoel.com/manoel-freitas/amare
|
||||||
# IMAGE_TAG=staging|production|<git-sha>
|
# IMAGE_TAG=staging|production|<git-sha>
|
||||||
# PostgreSQL is a separate Dokploy database service (not defined here).
|
# PostgreSQL is a separate Dokploy database service (not defined here).
|
||||||
# Traefik/Dokploy domains should target service `web` port 8000.
|
# Traefik/Dokploy domains should target service `web` port 8000.
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# Deploy Dokploy (staging → production)
|
# Deploy Dokploy (staging → production)
|
||||||
|
|
||||||
Runbook for operating Amare on a VPS with Dokploy connected to GitHub, publishing immutable images to GHCR.
|
Runbook for operating Amare on a VPS with Dokploy connected to Gitea (git.hellomanoel.com), publishing immutable images to Gitea's container registry.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
CI (main) → build FrankenPHP image → GHCR :<sha> + :staging
|
CI (main) → build FrankenPHP image → git.hellomanoel.com registry :<sha> + :staging
|
||||||
→ Dokploy staging compose.deploy
|
→ Dokploy staging compose.deploy
|
||||||
→ smoke /up / /admin/login
|
→ smoke /up / /admin/login
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ Promote (manual) → retag same digest as :production (no rebuild)
|
|||||||
|---|---|
|
|---|---|
|
||||||
| Compose file | [`docker-compose.deploy.yml`](../../docker-compose.deploy.yml) |
|
| Compose file | [`docker-compose.deploy.yml`](../../docker-compose.deploy.yml) |
|
||||||
| Processes | `migrate` (one-shot) → `web` / `queue` / `scheduler` |
|
| Processes | `migrate` (one-shot) → `web` / `queue` / `scheduler` |
|
||||||
| Image | `ghcr.io/<owner>/<repo>:<sha>` (+ aliases `:staging`, `:production`) |
|
| Image | `git.hellomanoel.com/manoel-freitas/amare:<sha>` (+ aliases `:staging`, `:production`) |
|
||||||
| Database | Dokploy PostgreSQL **per environment** (not in the app image) |
|
| Database | Dokploy PostgreSQL **per environment** (not in the app image) |
|
||||||
| Media | Cloudflare R2 (`FILESYSTEM_DISK=r2`), separate buckets per environment |
|
| Media | Cloudflare R2 (`FILESYSTEM_DISK=r2`), separate buckets per environment |
|
||||||
| Mail | Resend (`MAIL_MAILER=resend`) |
|
| Mail | Resend (`MAIL_MAILER=resend`) |
|
||||||
@@ -26,11 +26,14 @@ Promote (manual) → retag same digest as :production (no rebuild)
|
|||||||
|
|
||||||
## Prerequisites (manual)
|
## Prerequisites (manual)
|
||||||
|
|
||||||
1. Dokploy installed on the VPS; GitHub provider connected.
|
1. Gitea repository `manoel-freitas/amare` at `https://git.hellomanoel.com`; **Repository Actions enabled** in repo settings; a registered **Gitea Actions runner** (see [docs.gitea.com usage/actions/quickstart](https://docs.gitea.com/usage/actions/quickstart)) with an `ubuntu-latest` label.
|
||||||
2. GHCR registry in Dokploy (`ghcr.io`) with a PAT that can **read** packages (`read:packages`). Prefer a dedicated bot/token; do not store write tokens on the VPS.
|
2. Dokploy installed on the VPS.
|
||||||
3. Two PostgreSQL services in Dokploy (staging + production), private (no public port).
|
3. Container registry in Dokploy (`git.hellomanoel.com`) with a PAT that can **read** packages (`read:package`). Prefer a dedicated bot/token; do not store write tokens on the VPS. The **write** PAT (`write:package`) lives only in Gitea repo secrets for the pipeline.
|
||||||
4. Two R2 buckets (or prefixes) and Resend credentials for each environment as needed.
|
4. Two PostgreSQL services in Dokploy (staging + production), private (no public port).
|
||||||
5. Domains (or temporary Dokploy/traefik.me hosts) pointing at the VPS with TLS.
|
5. Two R2 buckets (or prefixes) and Resend credentials for each environment as needed.
|
||||||
|
6. Domains (or temporary Dokploy/traefik.me hosts) pointing at the VPS with TLS.
|
||||||
|
|
||||||
|
Note: Gitea's `GITEA_TOKEN` cannot push OCI packages ([gitea#23642](https://github.com/go-gitea/gitea/issues/23642)); registry authentication in the workflows uses a PAT (`GITEA_PAT`), not the token.
|
||||||
|
|
||||||
## Create Compose stacks
|
## Create Compose stacks
|
||||||
|
|
||||||
@@ -44,7 +47,7 @@ Create **two** Dokploy Compose services (same repo, same compose path):
|
|||||||
Dokploy Environment for each stack must set:
|
Dokploy Environment for each stack must set:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
APP_IMAGE=ghcr.io/<owner>/<repo>
|
APP_IMAGE=git.hellomanoel.com/manoel-freitas/amare
|
||||||
IMAGE_TAG=staging # or production
|
IMAGE_TAG=staging # or production
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -52,11 +55,11 @@ Point Dokploy domain(s) at service **`web`**, port **`8000`**. Do not publish Po
|
|||||||
|
|
||||||
Compose services must join the external Docker network `dokploy-network` (declared in `docker-compose.deploy.yml`) so they can resolve the Dokploy-managed Postgres internal host (e.g. `amare-stg-pez43e`). Set `DB_HOST` to that **Internal Host** from the Dokploy database UI — not a public hostname.
|
Compose services must join the external Docker network `dokploy-network` (declared in `docker-compose.deploy.yml`) so they can resolve the Dokploy-managed Postgres internal host (e.g. `amare-stg-pez43e`). Set `DB_HOST` to that **Internal Host** from the Dokploy database UI — not a public hostname.
|
||||||
|
|
||||||
Source can be GitHub (so Dokploy clones the compose file) or Raw paste of `docker-compose.deploy.yml`. Prefer GitHub + fixed compose path so updates stay in sync with `main`.
|
Source can be Gitea (so Dokploy clones the compose file) or Raw paste of `docker-compose.deploy.yml`. Prefer Gitea + fixed compose path so updates stay in sync with `main`.
|
||||||
|
|
||||||
## Required Laravel env (Dokploy only)
|
## Required Laravel env (Dokploy only)
|
||||||
|
|
||||||
Set these in Dokploy Environment UI (written to `.env` next to the compose file). **Never** put them in GitHub Actions secrets or image layers.
|
Set these in Dokploy Environment UI (written to `.env` next to the compose file). **Never** put them in Gitea Actions secrets or image layers.
|
||||||
|
|
||||||
```env
|
```env
|
||||||
APP_NAME=Amare
|
APP_NAME=Amare
|
||||||
@@ -120,9 +123,9 @@ Upload path does not need R2 CORS with the local temp-disk default. Still useful
|
|||||||
|
|
||||||
Also enable public access / custom domain for `R2_URL` so `<img>` URLs work after save.
|
Also enable public access / custom domain for `R2_URL` so `<img>` URLs work after save.
|
||||||
|
|
||||||
## GitHub Actions secrets
|
## Gitea Actions secrets
|
||||||
|
|
||||||
Repository secrets used by workflows:
|
Repository secrets (Gitea → Settings → Actions → Secrets) used by workflows:
|
||||||
|
|
||||||
| Secret | Purpose |
|
| Secret | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -132,32 +135,36 @@ Repository secrets used by workflows:
|
|||||||
| `DOKPLOY_PRODUCTION_COMPOSE_ID` | Production **Compose** service id (not an Application id) |
|
| `DOKPLOY_PRODUCTION_COMPOSE_ID` | Production **Compose** service id (not an Application id) |
|
||||||
| `STAGING_URL` | Public origin for staging smoke (e.g. `https://staging.example.com`) |
|
| `STAGING_URL` | Public origin for staging smoke (e.g. `https://staging.example.com`) |
|
||||||
| `PRODUCTION_URL` | Public origin for production smoke |
|
| `PRODUCTION_URL` | Public origin for production smoke |
|
||||||
|
| `GITEA_PAT` | Personal Access Token with `read:package` + `write:package` scopes — used to push images to `git.hellomanoel.com` |
|
||||||
|
| `GITEA_REGISTRY_USER` | Gitea username that owns `GITEA_PAT` (`manoel-freitas`) |
|
||||||
|
|
||||||
HTTP 404 from `compose.deploy` usually means the compose id is wrong (Application id instead of Compose) or `DOKPLOY_URL` still includes `/api`.
|
HTTP 404 from `compose.deploy` usually means the compose id is wrong (Application id instead of Compose) or `DOKPLOY_URL` still includes `/api`.
|
||||||
|
|
||||||
`GITHUB_TOKEN` (automatic) publishes to GHCR with `packages:write`. No Laravel/`APP_KEY`/DB/R2/Resend secrets belong in GitHub for this pipeline.
|
The automatic `GITEA_TOKEN` runs workflows but **cannot push OCI packages** ([gitea#23642](https://github.com/go-gitea/gitea/issues/23642)); registry authentication therefore uses `GITEA_PAT` + `GITEA_REGISTRY_USER`. No Laravel/`APP_KEY`/DB/R2/Resend secrets belong in Gitea for this pipeline.
|
||||||
|
|
||||||
## Workflows
|
## Workflows
|
||||||
|
|
||||||
|
Workflows live in [`.gitea/workflows/`](../../.gitea/workflows/).
|
||||||
|
|
||||||
### Staging (automatic)
|
### Staging (automatic)
|
||||||
|
|
||||||
[`.github/workflows/deploy-staging.yml`](../../.github/workflows/deploy-staging.yml)
|
[`.gitea/workflows/deploy-staging.yml`](../../.gitea/workflows/deploy-staging.yml)
|
||||||
|
|
||||||
1. Waits for workflow `CI` success on push to `main`.
|
1. Waits for workflow `CI` success (`workflow_run`) on push to `main`.
|
||||||
2. Builds once; pushes `:<full-sha>` and `:staging`.
|
2. Builds once; pushes `:<full-sha>` and `:staging` to the Gitea registry.
|
||||||
3. Calls Dokploy `compose.deploy` and polls until done.
|
3. Calls Dokploy `compose.deploy` and polls until done.
|
||||||
4. Runs [`scripts/deploy/smoke.sh`](../../scripts/deploy/smoke.sh) against `STAGING_URL`.
|
4. Runs [`scripts/deploy/smoke.sh`](../../scripts/deploy/smoke.sh) against `STAGING_URL`.
|
||||||
|
|
||||||
### Production (manual)
|
### Production (manual)
|
||||||
|
|
||||||
[`.github/workflows/promote-production.yml`](../../.github/workflows/promote-production.yml)
|
[`.gitea/workflows/promote-production.yml`](../../.gitea/workflows/promote-production.yml)
|
||||||
|
|
||||||
1. Operator runs **Actions → Promote production**.
|
1. Operator runs **Gitea → Actions → Promote production**.
|
||||||
2. Inputs: full `sha` already on GHCR; `confirm` must be exactly `PRODUCTION`.
|
2. Inputs: full `sha` already in the Gitea registry; `confirm` must be exactly `PRODUCTION`.
|
||||||
3. Retags the **same digest** as `:production` (no rebuild).
|
3. Retags the **same digest** as `:production` (no rebuild).
|
||||||
4. Deploys production compose + smoke.
|
4. Deploys production compose + smoke.
|
||||||
|
|
||||||
Private repos on GitHub Free do not get Environment required reviewers; human approval is the explicit `workflow_dispatch` + confirmation string. GitHub Pro Environment reviewers are optional later.
|
Human approval is the explicit `workflow_dispatch` + confirmation string (Gitea does not support GitHub Environment required reviewers).
|
||||||
|
|
||||||
## First admin and authorized production seeding
|
## First admin and authorized production seeding
|
||||||
|
|
||||||
@@ -256,10 +263,10 @@ No rebuild. Move the environment alias to a previous SHA digest and redeploy.
|
|||||||
### Staging
|
### Staging
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Locally or in a one-off Actions shell with GHCR login
|
# Locally or in a one-off Actions shell with Gitea registry login
|
||||||
docker buildx imagetools create \
|
docker buildx imagetools create \
|
||||||
--tag ghcr.io/<owner>/<repo>:staging \
|
--tag git.hellomanoel.com/manoel-freitas/amare:staging \
|
||||||
ghcr.io/<owner>/<repo>:<previous-sha>
|
git.hellomanoel.com/manoel-freitas/amare:<previous-sha>
|
||||||
|
|
||||||
# Then trigger Dokploy deploy (UI Deploy, or):
|
# Then trigger Dokploy deploy (UI Deploy, or):
|
||||||
DOKPLOY_URL=... DOKPLOY_API_KEY=... DOKPLOY_COMPOSE_ID=... \
|
DOKPLOY_URL=... DOKPLOY_API_KEY=... DOKPLOY_COMPOSE_ID=... \
|
||||||
@@ -297,7 +304,7 @@ Expects HTTP 200 for `/up`, `/`, and `/admin/login`.
|
|||||||
## Local validation of Compose
|
## Local validation of Compose
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
APP_IMAGE=ghcr.io/<owner>/<repo> IMAGE_TAG=staging \
|
APP_IMAGE=git.hellomanoel.com/manoel-freitas/amare IMAGE_TAG=staging \
|
||||||
docker compose -f docker-compose.deploy.yml config
|
docker compose -f docker-compose.deploy.yml config
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
Define immutable staging and production promotion through GHCR and Dokploy Compose, including migration, health, smoke, rollback, and backup evidence.
|
Define immutable staging and production promotion through the Gitea container registry (`git.hellomanoel.com`) and Dokploy Compose, including migration, health, smoke, rollback, and backup evidence.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
### Requirement: Staging deploys an immutable application image by commit SHA
|
### Requirement: Staging deploys an immutable application image by commit SHA
|
||||||
|
|
||||||
The system SHALL deploy staging from a single FrankenPHP application image tagged with the Git commit SHA and published to GHCR (SPEC §14.3, §15.2). Web, queue worker, and scheduler processes MUST use that same image digest. Rebuilds per process on the staging host MUST NOT be the promotion path.
|
The system SHALL deploy staging from a single FrankenPHP application image tagged with the Git commit SHA and published to the Gitea container registry (SPEC §14.3, §15.2). Web, queue worker, and scheduler processes MUST use that same image digest. Rebuilds per process on the staging host MUST NOT be the promotion path.
|
||||||
|
|
||||||
#### Scenario: Same image serves all application processes
|
#### Scenario: Same image serves all application processes
|
||||||
|
|
||||||
- **WHEN** a staging deployment is promoted for commit SHA `abc123`
|
- **WHEN** a staging deployment is promoted for commit SHA `abc123`
|
||||||
- **THEN** web, queue, and scheduler MUST run from `ghcr.io/<owner>/<repo>:abc123` (or equivalent digest)
|
- **THEN** web, queue, and scheduler MUST run from `git.hellomanoel.com/<owner>/<repo>:abc123` (or equivalent digest)
|
||||||
- **AND** MUST NOT rebuild distinct images per process
|
- **AND** MUST NOT rebuild distinct images per process
|
||||||
|
|
||||||
#### Scenario: CI publishes the image before Dokploy deploy
|
#### Scenario: CI publishes the image before Dokploy deploy
|
||||||
@@ -69,7 +69,7 @@ Production SHALL be promoted from an already-published SHA-tagged image without
|
|||||||
#### Scenario: Operator promotes a staging-approved SHA to production
|
#### Scenario: Operator promotes a staging-approved SHA to production
|
||||||
|
|
||||||
- **WHEN** the operator confirms promotion of commit SHA `abc123`
|
- **WHEN** the operator confirms promotion of commit SHA `abc123`
|
||||||
- **THEN** production web, queue, and scheduler MUST run the same digest previously published as `ghcr.io/<owner>/<repo>:abc123`
|
- **THEN** production web, queue, and scheduler MUST run the same digest previously published as `git.hellomanoel.com/<owner>/<repo>:abc123`
|
||||||
- **AND** MUST NOT rebuild from source for that promotion
|
- **AND** MUST NOT rebuild from source for that promotion
|
||||||
|
|
||||||
### Requirement: Database backups exist before production cutover
|
### Requirement: Database backups exist before production cutover
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# DOKPLOY_API_KEY x-api-key value
|
# DOKPLOY_API_KEY x-api-key value
|
||||||
# DOKPLOY_COMPOSE_ID target compose id
|
# DOKPLOY_COMPOSE_ID target compose id
|
||||||
# Optional env:
|
# Optional env:
|
||||||
# DEPLOY_TITLE deployment title (default: GitHub deploy)
|
# DEPLOY_TITLE deployment title (default: Gitea deploy)
|
||||||
# DEPLOY_TIMEOUT_SEC total wait seconds (default: 900)
|
# DEPLOY_TIMEOUT_SEC total wait seconds (default: 900)
|
||||||
# DEPLOY_POLL_SEC poll interval (default: 10)
|
# DEPLOY_POLL_SEC poll interval (default: 10)
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ DOKPLOY_URL="${DOKPLOY_URL%/}"
|
|||||||
DOKPLOY_URL="${DOKPLOY_URL%/api}"
|
DOKPLOY_URL="${DOKPLOY_URL%/api}"
|
||||||
DOKPLOY_URL="${DOKPLOY_URL%/}"
|
DOKPLOY_URL="${DOKPLOY_URL%/}"
|
||||||
|
|
||||||
DEPLOY_TITLE="${DEPLOY_TITLE:-GitHub deploy}"
|
DEPLOY_TITLE="${DEPLOY_TITLE:-Gitea deploy}"
|
||||||
DEPLOY_TIMEOUT_SEC="${DEPLOY_TIMEOUT_SEC:-900}"
|
DEPLOY_TIMEOUT_SEC="${DEPLOY_TIMEOUT_SEC:-900}"
|
||||||
DEPLOY_POLL_SEC="${DEPLOY_POLL_SEC:-10}"
|
DEPLOY_POLL_SEC="${DEPLOY_POLL_SEC:-10}"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user