EN /HU | Login

Deployment

The hosting and CI/CD environment: version control, continuous integration and web hosting, orchestrated by Docker Compose.

Components

Service Image Role
Gitea gitea/gitea:1.27.3 Self-hosted Git service; its own stack since 2026-10-23.
Woodpecker Server woodpeckerci/woodpecker-server:v3.19.0 CI/CD server, integrated with Gitea; its own stack since 2026-10-23.
Woodpecker Agent woodpeckerci/woodpecker-agent:v3.19.0 Runs the pipeline steps in containers through the Docker socket, up to 3 workflows at a time.
MySQL mysql:8 The softwares database: software metadata and releases.
API local build Rails 8 app: catalog REST API, file downloads, ActiveAdmin, /build/* endpoints. Routed for /api, /file, /build and /admin on teletypegames.org.
Frontend local build The Vue 3 / Vite game portal.

Shared storage

Artifacts arrive over HTTP: POST /build/upload writes into the API's artifact directory — ./data/softwares on the host, mounted as /softwares in the container (FILE_CONTAINER_PATH). The updater and /file/* static serving read the same directory.

Deployment workflow

A game repo's .woodpecker.yaml is a one-line marker (platform: <platform>, plus name: when the software name differs from the repo name). The full pipeline comes from the update server's Woodpecker configuration extension — see WarpEngine:

SH
curl "https://teletypegames.org/build/config?platform=<platform>"

The repo also needs metadata.json and the application_token Woodpecker secret. After building, the pipeline publishes in two steps.

1. Upload — one multipart curl request per file. Filenames must carry the <name>-<version> prefix.

SH
curl -X POST -H "X-Update-Secret: ${UPDATE_SECRET}" -F "file=@<name>-<version>.zip" \
  "${UPDATE_SERVER}/build/upload?name=...&version=..."

2. Publish — registers the new version.

SH
curl -X POST -H "X-Update-Secret: ${UPDATE_SECRET}" \
  "${UPDATE_SERVER}/build/publish?platform=...&name=...&version=..."

UPDATE_SECRET is a WarpEngine::ApplicationToken with the update + upload scopes — per-owner, optionally expiring, created in the admin.

Once published, the software and its releases appear on the teletypegames.org portal automatically.

Server stacks (ci-deploy)

The services on vps.teletype.hu deploy from Woodpecker too, but without host volumes or the Docker socket in the pipeline. The deploy step only connects to deploy@vps.teletype.hu with the repo's own key (secret deploy_ssh_key). On the server that key is bound to one fixed command, ci-deploy <target>, and the target's config in host-config (/etc/ci-deploy/<target>.conf) says what happens. The caller can pass nothing.

Target type What the server does Used for
compose-git clones master from Gitea itself, checks compose.yaml against the policy, copies the listed paths into the stack, docker compose up -d --build public repos: FTP server, LLM, Teletype BBS, Impostor BBS, Alexandria BBS, Feedia BBS, BBS Web, Statusbot, Updater, TTGWIKI exporter
compose-checkout fast-forwards the server's own git checkout from Gitea (read-only deploy key), policy check, docker compose up -d --build private repos: Redmine, Matomo, ResourceSpace; the wiki (there the wiki-fetcher syncs)
compose-services builds and replaces only the named services of a git checkout, runs migrations, health check teletypegames.org
git-publish static content presentation

The DOS store (the FTP server's APPS) has no deploy targets since WarpEngine 1.7: it is the WarpEngine's DOS shelf, rebuilt from the catalog on every publish.

The policy (ci-deploy-compose-policy) refuses privileged mode, extra capabilities, host namespaces, devices, the Docker socket, bind mounts outside the stack (except the paths the target allows, e.g. Matomo's read-only /var/log/nginx) and public ports the target does not list. So a push to master changes how a stack behaves, but does not give root.

Two things to know when writing a stack for it:

  • The deploy replaces files, it does not edit them. A single file mounted into a container keeps showing the old file after a deploy, and a directory listed in compose-git's copy list is replaced as a whole. Bake such files into the image, or mount the folder they are in. Where a mount has to stay (e.g. under an image's VOLUME), copy the files into the image too, so a change rebuilds it and the container is recreated.
  • Secrets stay on the server (.env, db.env); the pipeline's docker compose config check runs with a dummy one.

Stacks that are deployed by hand on purpose: Gitea and Woodpecker (CI must not deploy itself), Authentik (the single sign-on everything depends on) and the observability stack (it needs host-level mounts). Onboarding a new target is described in the VPS assistant notes (server documentation, section 9.20).