edit(content): review batch-1 editorial pass across 24 posts (EN + ZH)
- voice: first-person throughout (we/our -> I/my), matching the post guideline - privacy: internal LAN IPs replaced with placeholder hostnames (n8n.internal, gpu-node.internal); one compose volume path generalised - ZH twins: Traditional -> Simplified characters (site serves zh-Hans), whole-file renormalisation where the twin was still Traditional - dynamicCta: true on 12 posts so the contextual dual-track CTA renders - long configs wrapped in <details class="tech-deep-dive"> with a summary naming the artefact and line count - self-hosted-tts: token refresh now writes .tmp + atomic mv, with a note on the 401 race the old truncating redirect created
This commit is contained in:
1 parent
86edd40c74
commit
68771101ea
50 files changed
+478
-375
No files matched your search
@@ -5,6 +5,7 @@ pubDate: 2026-09-04
|
||||
category: case-studies
|
||||
blueprint: sme-digital-suite
|
||||
blueprintLayer: L1-high-conversion-web
|
||||
dynamicCta: true
|
||||
tags: [astro, gitea, ci-cd, self-hosting, docker, cloudflare]
|
||||
ogImage: /og/how-i-host-this-blog.jpg
|
||||
banner: /banners/how-i-host-this-blog.jpg
|
||||
@@ -39,7 +40,7 @@ git push → Gitea webhook → act_runner picks up the job
|
||||
→ npm ci → astro build → copy dist/* to docroot → nginx serves it
|
||||
```
|
||||
|
||||
The whole thing runs on hardware on the infrastructure I manage, which is exactly the point —
|
||||
The whole thing runs on the self-hosted production infrastructure I manage, which is exactly the point —
|
||||
this is a live demo of the DevOps work I describe elsewhere.
|
||||
|
||||
## The docker-compose setup
|
||||
@@ -48,6 +49,9 @@ Here's the shape of the stack. This is the local/origin side (Gitea + the
|
||||
runner + nginx); Cloudflare handles public exposure separately via a tunnel,
|
||||
so nothing here needs a public IP or open ports.
|
||||
|
||||
<details>
|
||||
<summary>docker-compose.yml — Gitea + act_runner + nginx (blog docroot) (40 lines)</summary>
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml — Gitea + act_runner + nginx (blog docroot)
|
||||
services:
|
||||
@@ -88,6 +92,8 @@ services:
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
The CI job (`deploy.yml`) builds `dist/` on the runner, then `cp -r dist/*`
|
||||
into the `./html` volume shared with the nginx container. Cloudflare's tunnel
|
||||
daemon (`cloudflared`) points at `nginx-blog` — that's the only thing exposed
|
||||
@@ -97,9 +103,7 @@ Two things worth calling out that cost me time:
|
||||
|
||||
1. **`act_runner` needs the Docker socket** (`/var/run/docker.sock`) mounted to
|
||||
spawn build jobs — without it, jobs sit queued forever.
|
||||
2. **The runner's `GITEA_RUNNER_REGISTRATION_TOKEN`** is a Gitea *Actions
|
||||
secret* (`${{ secrets.PAT }}` in the workflow), never a hardcoded value in
|
||||
the repo — so the repo stays safe to make public.
|
||||
2. **Strict credential externalization**: The runner's `GITEA_RUNNER_REGISTRATION_TOKEN` is injected via an untracked local host `.env` file (`${RUNNER_TOKEN}`), while the build checkout token lives in Gitea Actions Secrets (`${{ secrets.PAT }}` in the workflow). Nothing sensitive is hardcoded in the repo, keeping it safe to make public.
|
||||
|
||||
## The installation pain (real, from my commit history)
|
||||
|
||||
@@ -126,8 +130,10 @@ is a log of the gotchas. The four that cost real time:
|
||||
3. **Gitea ↔ `act_runner` version mismatch.** I burned three commits
|
||||
("test gitea 1.24.7 + act_runner 0.2.13" → "gitea 1.25.5" → "gitea 1.27.3 +
|
||||
runner 3.3.2") before the runner would register and pick up jobs. The
|
||||
lesson: **match the runner to the Gitea major version** — a runner one major
|
||||
version behind a newer Gitea silently fails to register.
|
||||
lesson: **track the Gitea Actions architecture migration** — Gitea 1.27+
|
||||
transitioned from legacy `act_runner` to `gitea/runner` (the 3.x branch with
|
||||
revised container volume directives). When the runner image or protocol falls
|
||||
behind the server instance, jobs fail silently or refuse to spawn containers.
|
||||
|
||||
4. **`config_file` env for the runner.** The runner needed its config path
|
||||
passed explicitly before it would connect to the right instance.
|
||||
|
||||
Reference in new issue
Block a user