---
title: Deployment
url: 'https://wiki.teletypegames.org/infrastructure/deployment'
markdown: 'https://wiki.teletypegames.org/infrastructure/deployment.md'
lang: en
date: '2026-06-03'
description: '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.…'
taxonomy:
  tag:
    - howto
---

# Deployment

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

# Components

| Service | Image | Role |
|---|---|---|
| **[Gitea](https://wiki.teletypegames.org/infrastructure/gitea)** | `gitea/gitea:1.27.3` | Self-hosted Git service; its own stack since 2026-10-23. |
| **[Woodpecker](https://wiki.teletypegames.org/infrastructure/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](https://wiki.teletypegames.org/engines/warp-engine):

```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](https://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](https://git.teletypegames.org/infra/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](https://wiki.teletypegames.org/infrastructure/ftp-server), [LLM](https://wiki.teletypegames.org/infrastructure/llm), [Teletype BBS](https://wiki.teletypegames.org/services/teletype-bbs), [Impostor BBS](https://wiki.teletypegames.org/services/impostor-bbs), [Alexandria BBS](https://wiki.teletypegames.org/services/alexandria-bbs), [Feedia BBS](https://wiki.teletypegames.org/services/feedia-bbs), [BBS Web](https://wiki.teletypegames.org/services/bbsweb), [Statusbot](https://wiki.teletypegames.org/services/statusbot), [Updater](https://wiki.teletypegames.org/services/updater), [TTGWIKI](https://wiki.teletypegames.org/dos/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](https://wiki.teletypegames.org/infrastructure/matomo), [ResourceSpace](https://wiki.teletypegames.org/infrastructure/resourcespace); the [wiki](https://wiki.teletypegames.org/services/wiki-site) (there the wiki-fetcher syncs) |
| `compose-services` | builds and replaces only the named services of a git checkout, runs migrations, health check | [teletypegames.org](https://wiki.teletypegames.org/services/public-site) |
| `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](https://wiki.teletypegames.org/engines/warp-engine)'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](https://wiki.teletypegames.org/infrastructure/gitea) and [Woodpecker](https://wiki.teletypegames.org/infrastructure/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](https://wiki.teletypegames.org/infrastructure/vps-assistant) notes (server documentation, section 9.20).

'; var prevLevel = 2; for (var i = 0; i < headings.length; i++) { var h = headings\[i\]; var text = h.textContent.trim(); var slug = text.toLowerCase() .replace(/\[áàâä\]/g,'a').replace(/\[éèêë\]/g,'e').replace(/\[íìîï\]/g,'i') .replace(/\[óòôöő\]/g,'o').replace(/\[úùûüű\]/g,'u') .replace(/\[^a-z0-9\]+/g, '-').replace(/^-|-$/g, ''); if (!slug) slug = 'heading-' + i; if (used\[slug\]) { slug += '-' + i; } used\[slug\] = true; h.id = slug; var level = parseInt(h.tagName.charAt(1)); var cls = level > 2 ? ' class="toc-child"' : ''; html += '- [' + text + '](#'%20+%20slug%20+%20')
'; } html += '
'; toc.innerHTML = html; // Scroll-spy: highlight current heading var links = toc.querySelectorAll('a'); var ids = \[\]; for (var j = 0; j < links.length; j++) ids.push(links\[j\].getAttribute('href').slice(1)); function updateActive() { var current = ''; for (var k = 0; k < ids.length; k++) { var el = document.getElementById(ids\[k\]); if (el && el.getBoundingClientRect().top <= 80) current = ids\[k\]; } for (var m = 0; m < links.length; m++) { links\[m\].parentNode.classList.toggle('active', links\[m\].getAttribute('href') === '#' + current); } } window.addEventListener('scroll', updateActive, { passive: true }); updateActive(); })();

---

## Navigation

- Parent: [Infrastructure](https://wiki.teletypegames.org/infrastructure.md)
- Previous: [Backup](https://wiki.teletypegames.org/infrastructure/backup.md)
- Next: [FTP server](https://wiki.teletypegames.org/infrastructure/ftp-server.md)
