Chapter 10

A push to your repository deploys the site.

Chapter 04 got your code on by hand. This is the automated version: connect the site to a Gitea repository once, and every push runs your build and publishes it — with visitors seeing a short maintenance page instead of a half-updated site.

How it runs, and why that is safe

When deployments are enabled, a runner is registered for your repository and runs inside your site's own container. That means it already has your app's tooling — PHP, Composer, the bundled Node — and it reloads the site through the container's own supervisor. It never touches the box's Docker, and a push to your repo can only ever affect your site. There is no shared build farm to queue behind and nothing your job can reach beyond its own container.

Turning it on

Enabling is an operator action in the panel — on your site's Deployments page they pick your repository and switch it on. You do not need a panel account for the deploy itself; you interact with it entirely through your repository.

Two things come back to you from that page:

  1. A runner label — the value your workflow targets with runs-on:. It is your site's container name, e.g. no-app-example-com.
  2. Confirmation that the runner is registered and idle, waiting for your first push.

Because the runner is registered before your repo has anything in it, your very first push is an ordinary deploy — there is no separate "seed the folder" step. Checkout authenticates automatically; you do not add a deploy key or store any secret.

The workflow

Copy the example matching your image into your repo as .gitea/workflows/deploy.yml and fill in the label and your domain. The full files are in .gitea/workflows-examples/ (a Laravel one and a static one). The shape is the same for both:

jobs:
  deploy:
runs-on: no-app-example-com          # your runner label

steps:
  - run: nimbus deploy:begin         # maintenance page on, workers stopped
  - uses: actions/checkout@v4
  # … install, build, publish, migrate …
  - run: nimbus deploy:end           # new code live, workers back, page off
    if: always()
Building this with an AI agent?

There is a companion file written for coding agents, AGENTS.md (next to this guide). It states the whole deploy model, the exact facts an agent needs from you — the runner label, the domain, the image type — and ready-to-adapt workflow templates. Drop it into your repository as AGENTS.md, or point your agent at it, and it can write the workflow itself.

The two hooks, and the one rule

nimbus deploy:begin and nimbus deploy:end bracket the risky middle of a deploy. Between them the site answers every request with a maintenance page (a 503), so no visitor ever sees a half-published folder or a half-run migration, and your background workers are stopped so none runs old code against a freshly migrated database. deploy:end then reloads PHP — which is what makes your new code actually take effect — restarts the workers, and drops the maintenance page.

Always close the window

The closing hook must carry if: always(). Without it, a step that fails — a red test, a broken build — would stop the workflow before deploy:end runs, and your site would sit behind the maintenance page. With it, the page always lifts and visitors get the previous working site back. As a safety net the platform also clears a stuck window automatically after a few minutes, but do not rely on that — it is the last resort, not the plan.

What you do not have to do

No restart from the panel after a normal deploy — deploy:end already reloaded PHP, so the opcache is reset and your new code is live. Even a push that adds a background worker for the first time takes effect without a restart. A restart is only needed the first time deployments are enabled, and that is the operator's one click.

When it does not deploy

  • The maintenance page stays up. A job failed after deploy:begin and either did not reach deploy:end or you omitted if: always(). Fix the workflow; the window clears itself after the deadline in the meantime.
  • Nothing happens on push. Check the branch in your workflow's on: push matches the branch you pushed, and that runs-on: is exactly the label from the Deployments page.
  • A change is not visible. Confirm your publish step actually wrote into the live folder (/var/www/virtual/<your-domain>) — the runner's checkout lands in a work directory, not the docroot, until you copy it across.