ci: deploy the verified local image
All checks were successful
Test and deploy / Verify production build (push) Successful in 10m25s

Build and test the Next.js application once with persistent BuildKit caches, smoke-test the immutable runtime image, and activate that same image through the shared production Docker daemon with serialized rollback-safe deployment.
This commit is contained in:
2026-09-15 15:00:19 +03:30
parent 46495eb927
commit dcce6d219d
8 changed files with 319 additions and 130 deletions

View File

@@ -1,20 +1,27 @@
# Deployment
The Gitea Actions workflow in `.gitea/workflows/ci-cd.yml` verifies every pull
request and every push to `main`. A successful push to `main` is then deployed
over SSH. The server builds the exact Git archive tested by CI, starts the
Compose service, and waits for its container health check.
request and every push to `main`. A successful push to `main` builds one
verified Docker image on the shared host daemon, smoke-tests it, and activates
that exact commit-tagged image over SSH without uploading source or rebuilding.
Releases are stored in `<DEPLOY_PATH>/releases/<commit-sha>`. The `current`
Release manifests are stored in `<DEPLOY_PATH>/releases/<commit-sha>`. Each
manifest records the immutable image tag and Gitea run number. The `current`
symlink is changed only after the new container is healthy. If startup fails,
the deployment script automatically rebuilds and starts the previous release.
the deployment script restarts the previous image without rebuilding it.
## Gitea Actions setup
Enable **Repository Actions** in the repository settings and register a trusted
Gitea Runner. The workflow uses the `ubuntu-latest` label; that runner image must
provide Bash, Git, `tar`, OpenSSH (`ssh` and `scp`), and standard GNU utilities.
Node.js 22 is installed by the workflow.
Enable **Repository Actions** in the repository settings and register the
dedicated trusted runner maintained in
`https://git.amiirkhl.ir/amiirkhl/portfolio-gitea-runner`. The
workflow uses the `portfolio-ci` label. Its reusable job image provides Node.js
22, Bash, Git, OpenSSH, Docker CLI, Buildx, and Compose. Runner capacity remains
one because Gitea, builds, and production share a 2-core/4-GB server.
The runner and job container use the host Docker socket. Restrict the label to
this trusted repository and disable Actions for fork pull requests. The workflow
also skips pull requests whose head repository differs from this repository.
After the first successful verification run, protect `main` in Gitea and make
the **Verify production build** status check required. This prevents a pull
@@ -46,8 +53,9 @@ the protection against a man-in-the-middle attack.
## One-time server setup
The server needs Docker Engine with the Compose v2 plugin. The deployment user
must be able to run `docker` without an interactive password prompt.
The server needs Docker Engine with the Compose v2 and Buildx plugins. The
runner and deployment user must reach the same Docker daemon, and the deployment
user must run `docker` without an interactive password prompt.
Create the deployment root and the external network once:
@@ -88,9 +96,30 @@ optimizer-cache invalidation API.
For an emergency manual deployment from a checkout on the server:
```sh
docker compose --project-name portfolio up --detach --build --remove-orphans
docker compose \
--project-name portfolio \
--file compose.yaml \
--file compose.build.yaml \
up --detach --build --remove-orphans
```
This legacy path builds `amiirkhl-portfolio:latest` and is intentionally kept
outside the normal CI deployment. Existing source-based release directories can
still be started with their original Compose file during migration. Automatic
rollback begins with the first successful image-based release because older
release directories do not contain `release.env` image metadata.
## Pipeline timing verification
After migrating the runner, record one cold run and three normal source-change
runs. Expand the **Set up job** and **Complete job** sections in Gitea when
capturing the results. For each run, record checkout, verification build, audit,
runtime image creation, smoke test, deployment health wait, and completion time.
All three warm runs should finish below 180 seconds without OOM events or
production health failures. If they do not, collect `docker stats`, `free -h`,
`df -h`, `docker system df`, `docker buildx du --builder portfolio-builder`, and
the runner logs before changing or removing any quality gate.
Old versioned release directories are deliberately retained for rollback. They
can be removed manually after a newer release has been proven stable; keep the
directory referenced by the `current` symlink.