API Gateway
api-gateway (VMID 117, 10.0.0.17) runs the public ABAIR API, served at api.abair.ie/v4/. The gateway checks API keys and credits, applies rate limits, then forwards each request to the right back-end service.
| Playbook | ansible/hetzner-main/setup-api-gateway.yml |
| Settings | ansible/hetzner-main/vars/api-gateway.yml |
| Stack directory | /opt/api-gateway |
| Make targets | setup-api-gateway, update-api-gateway, enroll-wazuh-api-gateway |
| Secrets | Docker Registry Password, Webserver htpasswd admin, and the Secure Note api-gateway .env.secrets |
Containers
The playbook writes a Docker Compose file that runs four containers. The images come from the private registry on docker-build.
| Container | Image | Port on the VM | Purpose |
|---|---|---|---|
api-gateway | api-gateway-production:main | 4004 | The gateway. Memory limit 2 GB. Writes logs to /opt/api-gateway/logs/. |
api-authentication | api-authentication:main | 4005 | Authentication service, called by the gateway. |
api-gateway-docs | api-gateway-docs:main | 5100 | The API documentation site (Next.js). |
redis | redis:alpine | not published | Cache and rate-limit state for the gateway. |
The webserver routes api.abair.ie like this:
| Path | Goes to |
|---|---|
/v4/ | api-gateway (4004). /metrics and apikey/revoke are blocked from outside. |
/v4/recognition/streaming | api-gateway (4004), as a WebSocket |
/v4/docs and / | api-gateway-docs (5100) |
/v3/ | the legacy v3 API (abair-api) on services-user |
Back-end services
The gateway reaches its back ends directly over the private networks, never through the webserver. The addresses are set in vars/api-gateway.yml and written to /opt/api-gateway/.env.
| Setting | Address | Service |
|---|---|---|
SYNTHESIS | 192.168.100.2:8004 | Speech synthesis on Amergin |
RECOGNITION_BATCH | 192.168.100.2:8002 | Batch speech recognition on Amergin |
RECOGNITION_STREAMING | 192.168.100.2:8080 | Streaming speech recognition (Triton) on Amergin |
SUBTITLES | 192.168.100.2:4040 | Fotheidil API on Amergin |
PREDICTIVE_TEXT | 192.168.100.2:8003 | Predictive text on Amergin |
POS_TAGGER | 10.0.0.19:6789 | irishfst on services-staff |
BUNAMO | 10.0.0.19:9000 | bunamo on services-staff |
GRAMSRV | 10.0.0.19:10001 | gramsrv on services-staff |
GRAMADOIR | 10.0.0.19:10002 | gramadoir on services-staff |
AUTH | api-authentication:4005 | The authentication container |
SUPABASE_URL | Supabase (hosted) | Database and user accounts |
The same file also sets the rate limiter (100 requests per minute), the daily credit limit for anonymous IPs (3840), and a circuit breaker, which is turned off.
Amergin and services-staff both allow 10.0.0.17 through their firewalls for these ports.
Secrets
Secrets live in /opt/api-gateway/.env.secrets. The playbook fetches that file from the Vaultwarden Secure Note api-gateway .env.secrets, using your unlocked bw session, and writes it with owner root:deploy, mode 0640. If the note is missing, or you don't have access to it, the playbook stops.
Deployments
- Code changes are deployed by the gateway's own CI. The pipeline builds the image on the GitHub runner on docker-build, pushes it to the registry, then connects as the
deployuser and runsdocker compose pull api-gateway && docker compose up -d api-gateway. The playbook authorizes the runner's SSH key for that user. - Configuration changes (anything in
vars/api-gateway.ymlor the compose file) needmake update-api-gateway. That pulls every image and recreates changed containers.
Firewall
UFW allows SSH from anywhere, and ports 4004 and 4005 from the webserver only. The UFW rules are applied by the setup tag only, so update-api-gateway doesn't change them.
There is no UFW rule for port 5100, yet the docs site works through the webserver. That's because ports published by Docker bypass UFW's input rules. The same applies to 4004 and 4005: UFW is not what actually restricts them. See Networking → Firewalls.
Other details
- The VM has a 2 GB swap file. Its memory was raised from 2 GB to 4 GB after an out-of-memory incident on 2026-08-11, when a burst of subtitle uploads got the gateway process killed.
- Alloy ships the gateway's log file and scrapes
/metricsfrom bothapi-gatewayandapi-authentication.