Worker (Gateway) — Docker
This guide walks through running the Apinizer Worker (the API Gateway runtime) as a Docker container. The Worker terminates HTTP / HTTPS / gRPC / WebSocket requests, runs the deployed proxy policy pipelines, and emits traffic logs.
The Worker image is built on Ubuntu noble (glibc) — not Alpine — because the brotli4j native compression library has no musl build. Do not rebuild against Alpine.
Requirements
- A Linux x86-64 host with Docker Engine 24+
- The same MongoDB instance used by the Manager
- An Environment entry registered in the Manager UI (Server Management → Environments)
- A reachable Apinizer Cache (Hazelcast cluster) if you use quotas, OIDC sessions, circuit breakers
- Free TCP ports on the host:
8091/tcp— HTTP gateway listener8443/tcp— HTTPS gateway listener (when enabled in the Environment)
1. Pull the image
VERSION=2026.04.5
docker pull apinizercloud/worker:${VERSION}
2. Configure
Required:
| Variable | Description |
|---|---|
SPRING_DATA_MONGODB_URI | MongoDB connection string |
SPRING_DATA_MONGODB_DATABASE | MongoDB database name |
APINIZER_ENVIRONMENT_NAME | Environment name. Must exactly match an Environment in the Manager UI. |
WORKER_TIMEZONE | Timezone offset for log timestamps (e.g. +03:00) |
Recommended:
| Variable | Default | Description |
|---|---|---|
JAVA_OPTS | empty | "-server -XX:MaxRAMPercentage=75.0 -Dhttp.maxConnections=4096 -Dlog4j.formatMsgNoLookups=true" for production |
Optional (Undertow / gateway tuning):
| Variable | Default | Description |
|---|---|---|
tuneWorkerThreads | 1024 | XNIO worker pool |
tuneWorkerMaxThreads | 4096 | Hard cap on the worker pool |
tuneIoThreads | 4 | XNIO I/O selectors (set to vCPU count) |
tuneBufferSize | 16384 | Read/write buffer size (bytes) |
tuneDirectBuffers | true | Off-heap buffers (less GC pressure) |
tuneBacklog | 10000 | Kernel TCP listen backlog |
APINIZER_WORKER_NODE_IP | auto | Override the reported node IP (multi-NIC hosts) |
3. Run the container
docker run -d \
--name apinizer-worker \
-p 8091:8091 \
-p 8443:8443 \
--memory=8g \
--cpus=4 \
-e SPRING_DATA_MONGODB_URI='mongodb://user:pass@mongo:25080/?authSource=admin' \
-e SPRING_DATA_MONGODB_DATABASE='apinizer' \
-e APINIZER_ENVIRONMENT_NAME='prod-dc1' \
-e WORKER_TIMEZONE='+03:00' \
-e JAVA_OPTS='-server -XX:MaxRAMPercentage=75.0 -Dhttp.maxConnections=4096 -Dlog4j.formatMsgNoLookups=true' \
-v apinizer-worker-logs:/app/logs \
--restart unless-stopped \
--ulimit nofile=1048576:1048576 \
apinizercloud/worker:${VERSION}
The Worker is throughput-sensitive: it benefits from --ulimit nofile=1048576:1048576 for high concurrent connection counts.
4. docker-compose
services:
worker:
image: apinizercloud/worker:2026.04.5
container_name: apinizer-worker
restart: unless-stopped
mem_limit: 8g
cpus: 4
ports:
- "8091:8091"
- "8443:8443"
environment:
SPRING_DATA_MONGODB_DATABASE: apinizer
APINIZER_ENVIRONMENT_NAME: prod-dc1
WORKER_TIMEZONE: "+03:00"
JAVA_OPTS: "-server -XX:MaxRAMPercentage=75.0 -Dhttp.maxConnections=4096 -Dlog4j.formatMsgNoLookups=true"
tuneWorkerThreads: "1024"
tuneIoThreads: "4"
env_file:
- ./secrets/worker.env
ulimits:
nofile:
soft: 1048576
hard: 1048576
volumes:
- worker-logs:/app/logs
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8091/server-status | grep -q OK || exit 1"]
interval: 30s
timeout: 5s
retries: 5
start_period: 60s
volumes:
worker-logs:
5. Verify
curl -fsS http://127.0.0.1:8091/server-status
Send a test request through a deployed proxy:
curl -fsS http://127.0.0.1:8091/echo -H 'X-Trace: 1'
Expected boot logs:
ApiGateway standalone=true (env: prod-dc1)
HTTP listener bound: 0.0.0.0:8091
HTTPS listener bound: 0.0.0.0:8443 (only if Environment enables HTTPS)
Loaded N proxies for environment prod-dc1
6. Operations
| Action | Command |
|---|---|
| Status | docker ps --filter name=apinizer-worker |
| Logs | docker logs -f apinizer-worker |
| Restart | docker restart apinizer-worker |
| Stop (drain) | docker stop -t 60 apinizer-worker |
Use a generous stop timeout (docker stop -t 60) so in-flight HTTP / gRPC / WebSocket requests can drain. Without -t, Docker kills with SIGTERM after 10 s.
7. Networking
Inbound
| Port | Purpose |
|---|---|
8091 | HTTP gateway |
8443 | HTTPS gateway |
Outbound
- MongoDB (proxy config + traffic log writes)
- Cache cluster on
5701/tcp(Hazelcast wire) and8090/tcp(REST), when quotas/OIDC/CB are used - Each backend service every deployed proxy is wired to
8. Multi-Worker (horizontal scale)
Run the same image with the same APINIZER_ENVIRONMENT_NAME on each replica. Quota counters, OIDC sessions and circuit-breaker state are shared through the Apinizer Cache (Hazelcast). Without a shared Cache, each Worker has its own counters.
9. Resources
| Workload | mem_limit | cpus |
|---|---|---|
| Smoke / staging (<200 rps) | 2 GB | 2 |
| Production (1–3 krps per Worker) | 4–6 GB | 4 |
| High throughput (5+ krps) | 8 GB | 4–8 |
Always load-test on the target hardware before changing JVM flags.
Troubleshooting
APINIZER_ENVIRONMENT_NAME mismatch → container starts but proxies don't load
If the name does not match an Environment in the Manager UI exactly (case sensitive), no proxy snapshot is fetched and every request returns 404. Fix: correct the env var or create the Environment.
UnsatisfiedLinkError: brotli
You are running a custom build of the Worker on Alpine. Switch back to the official apinizercloud/worker image (Ubuntu noble base).
EMFILE: too many open files
Add --ulimit nofile=1048576:1048576 to docker run. Also check that /etc/security/limits.conf / the host's LimitNOFILE allows it.