apix-demo, apix-portal/src, apix-spider/src, apix-registry/src, apix-common/src were never staged. Without them the CI build has no source to compile and the Docker images cannot be produced. Also adds docs/ (infrastructure notes) missed in prior commits. Co-Authored-By: Mira <noreply@anthropic.com>
26 KiB
APIX Registry — Infrastructure Setup Guide
Complete walkthrough for deploying the APIX registry to a Hetzner VPS with Bunny.net CDN, Prometheus/Grafana observability, and live Loki telemetry.
Table of Contents
- Architecture Overview
- Prerequisites
- VPS Provisioning (Hetzner)
- DNS Configuration
- Server Bootstrap
- Application Build
- Environment Configuration
- Deploy the Stack
- Caddy TLS Reverse Proxy
- Bunny.net CDN Setup
- Live Telemetry: Promtail → Loki
- Grafana Dashboards
- Weekly Analytics (Bunny.net Logs)
- Verification Checklist
- Routine Operations
1. Architecture Overview
Internet
│
▼
Bunny.net CDN (100+ PoPs, GDPR-compliant, European)
│ Cache-Control headers respected; query-string vary cache enabled
│ HIT: served from edge (~5ms). MISS: forwarded to VPS
│
▼
Hetzner VPS (Helsinki / Falkenstein)
│
├── Caddy (80/443) ─── TLS termination, HTTPS redirect, rate limiting
│ │
│ ├── registry:8180 — REST API (Quarkus, JVM)
│ ├── portal:8081 — Web UI (Quarkus, Qute templates)
│ └── grafana:3000 — Dashboards (internal access only)
│
├── db:5432 — PostgreSQL 16 (no public port)
├── spider:8082 — Liveness checker (no public port)
├── prometheus:9090 — Metrics scraper (no public port)
└── promtail:9080 — Syslog receiver → Loki (port 5514 open)
Grafana Cloud (Loki)
│ Real-time: Bunny.net → TCP Syslog → Promtail → Loki → Grafana
└── Every CDN request visible within 1-2 seconds
CDN governance constraint: Cloudflare and AWS CloudFront must never be used. Both are founding member candidates — operating infrastructure gives governance leverage regardless of the founding charter. Approved providers: Bunny.net (primary), Fastly (fallback).
2. Prerequisites
Local machine
| Tool | Version | Purpose |
|---|---|---|
| Java (Temurin) | 21 | Building application JARs |
| Maven | 3.9+ | Build system |
| Docker Desktop | latest | Local dev + image building |
curl |
any | Script calls to Bunny.net API |
python3 |
3.8+ | JSON parsing in setup scripts |
Install Java 21 via SDKMAN:
curl -s https://get.sdkman.io | bash
sdk install java 21.0.3-tem
Accounts required
| Service | What you need |
|---|---|
| Hetzner Cloud | API token for VPS creation (optional — can provision manually) |
| Bunny.net | Account + API key (Account → API) |
| Grafana Cloud | Free tier sufficient; Loki + Prometheus endpoints |
| Domain registrar | Control over DNS for api-index.org |
3. VPS Provisioning (Hetzner)
Recommended spec
Type: CPX21 (3 vCPU, 4 GB RAM) — sufficient for MVP
Location: Helsinki (hel1) or Falkenstein (fsn1)
OS: Ubuntu 24.04 LTS
Network: Primary IPv4 + IPv6 dual stack
Backups: Enable automatic backups (adds 20% to monthly cost)
The CPX21 comfortably runs the full Docker stack (registry + spider + portal + db + prometheus + grafana + caddy + promtail) under MVP load. Upgrade to CPX31 if Prometheus retention or portal traffic grows.
Firewall rules (Hetzner firewall or ufw)
| Port | Protocol | Source | Purpose |
|---|---|---|---|
| 22 | TCP | Your IP only | SSH |
| 80 | TCP | any | Caddy HTTP→HTTPS redirect |
| 443 | TCP+UDP | any | Caddy HTTPS + HTTP/3 |
| 5514 | TCP | Bunny.net IPs | Promtail syslog receiver |
| 9090 | TCP | VPS localhost | Prometheus (internal only) |
| 3000 | TCP | VPS localhost | Grafana (access via Caddy or SSH tunnel) |
Bunny.net syslog source IPs: Bunny.net does not publish a static IP list; open 5514
to 0.0.0.0/0 and rely on the Promtail pipeline to discard unexpected traffic.
The syslog format is the only authentication layer needed at this volume.
SSH hardening (run as root after first login)
# Create deploy user
useradd -m -s /bin/bash deploy
usermod -aG sudo,docker deploy
# Copy your SSH public key
mkdir -p /home/deploy/.ssh
echo "YOUR_PUBLIC_KEY_HERE" > /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
# Disable root SSH + password auth
sed -i 's/^PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshd
4. DNS Configuration
Required records
| Name | Type | Value | TTL |
|---|---|---|---|
api-index.org |
A | VPS IPv4 | 300 |
api-index.org |
AAAA | VPS IPv6 | 300 |
www.api-index.org |
CNAME | api-index.org |
3600 |
Set TTL to 300 (5 min) before the cutover so propagation is fast. After CDN is live (Step 10), change the A/AAAA records to the Bunny.net CNAME instead.
After CDN setup (replace A records)
api-index.org CNAME <bunnynet-cname>.b-cdn.net
Caddy still handles TLS on the VPS. Bunny.net terminates the edge TLS and forwards to the VPS over HTTPS using the Caddy certificate.
5. Server Bootstrap
SSH in as deploy and run:
# 1. System update
sudo apt-get update && sudo apt-get upgrade -y
# 2. Docker (official repo — apt package is outdated)
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# 3. Add deploy user to docker group (logout + login to apply)
sudo usermod -aG docker deploy
# 4. Install Promtail (for Loki integration — see Step 11)
PROMTAIL_VERSION="3.0.0"
wget -q "https://github.com/grafana/loki/releases/download/v${PROMTAIL_VERSION}/promtail-linux-amd64.zip"
unzip -q promtail-linux-amd64.zip
sudo mv promtail-linux-amd64 /usr/local/bin/promtail
sudo chmod +x /usr/local/bin/promtail
rm promtail-linux-amd64.zip
# 5. Clone the repository
git clone https://gitea.your-server.example/botstandards/apix-mvp.git /opt/apix
# Or using GitHub mirror during MVP phase:
git clone https://github.com/your-org/apix-mvp.git /opt/apix
cd /opt/apix
6. Application Build
Build JARs locally and copy to the VPS, or build directly on the VPS if Java 21 is installed.
Build locally (recommended for CI phase)
# On your dev machine:
cd bot-service-index/apix-mvp
mvn clean package -DskipTests
# Copy artifacts to VPS
scp apix-registry/target/quarkus-app/ deploy@<vps-ip>:/opt/apix/apix-registry/target/quarkus-app/ -r
scp apix-spider/target/quarkus-app/ deploy@<vps-ip>:/opt/apix/apix-spider/target/quarkus-app/ -r
scp apix-portal/target/quarkus-app/ deploy@<vps-ip>:/opt/apix/apix-portal/target/quarkus-app/ -r
Build on VPS (MVP shortcut)
# Install Java 21 on VPS
sudo apt-get install -y wget
wget -q "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.3%2B9/OpenJDK21U-jdk_x64_linux_hotspot_21.0.3_9.tar.gz" \
-O /tmp/jdk21.tar.gz
sudo mkdir -p /opt/java
sudo tar xzf /tmp/jdk21.tar.gz -C /opt/java
sudo ln -sf /opt/java/jdk-21.0.3+9/bin/java /usr/local/bin/java
sudo ln -sf /opt/java/jdk-21.0.3+9/bin/javac /usr/local/bin/javac
# Install Maven
sudo apt-get install -y maven
# Build
cd /opt/apix
mvn clean package -DskipTests
Docker images
Dockerfiles are defined in WORKLOG Block 5 (I-04 to I-06) and not yet created.
Until they exist, run the JARs directly via quarkus:dev or write minimal Dockerfiles:
# infra/Dockerfile.registry (placeholder until Block 5)
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY apix-registry/target/quarkus-app/ quarkus-app/
EXPOSE 8180
ENTRYPOINT ["java", "-jar", "quarkus-app/quarkus-run.jar"]
Repeat for Dockerfile.spider (port 8082) and Dockerfile.portal (port 8081).
Build and tag:
cd /opt/apix
docker build -f infra/Dockerfile.registry -t apix-registry:latest .
docker build -f infra/Dockerfile.spider -t apix-spider:latest .
docker build -f infra/Dockerfile.portal -t apix-portal:latest .
7. Environment Configuration
Create /opt/apix/.env from the template below. This file is in .gitignore — never commit it.
# /opt/apix/.env — production values
# ── Database ──────────────────────────────────────────────────────────────────
APIX_DB_USER=apix
APIX_DB_PASSWORD=<generate: openssl rand -base64 32>
APIX_DB_NAME=apix
APIX_DB_PORT=5432 # Only exposed inside Docker network in production
# ── API security ─────────────────────────────────────────────────────────────
# Used to authenticate write requests (POST /services, PATCH /services/*, etc.)
# Rotate this key when onboarding new registrars.
APIX_API_KEY=<generate: openssl rand -hex 32>
# ── Registry identity ─────────────────────────────────────────────────────────
APIX_REGISTRY_BASE_URL=https://api-index.org
APIX_REGISTRY_NAME=APIX Registry
APIX_REGISTRY_DESCRIPTION=The open autonomous agent service discovery registry.
# ── Verification integrations ────────────────────────────────────────────────
GLEIF_API_URL=https://api.gleif.org/api/v1
OPENCORPORATES_API_KEY=<your OpenCorporates API key — leave blank if not yet contracted>
APIX_VERIFICATION_TIMEOUT_MS=5000
# ── Mail signing (Ed25519) ────────────────────────────────────────────────────
# Leave blank on first deploy — ephemeral key generated at startup.
# Set before production: openssl genpkey -algorithm ed25519 | ...
APIX_MAIL_SIGNING_PRIVATE_KEY=
APIX_MAIL_SIGNING_PUBLIC_KEY=
APIX_MAIL_SIGNING_KID=2026-05
# ── Spider ────────────────────────────────────────────────────────────────────
SPIDER_INTERVAL_MINUTES=15
# ── Grafana ───────────────────────────────────────────────────────────────────
GRAFANA_ADMIN_PASSWORD=<generate: openssl rand -base64 16>
GRAFANA_ROOT_URL=https://grafana.api-index.org # or http://localhost:3000 if SSH tunnel only
# ── Logging ───────────────────────────────────────────────────────────────────
LOG_LEVEL=INFO
Generate secrets in one pass:
echo "APIX_DB_PASSWORD=$(openssl rand -base64 32)"
echo "APIX_API_KEY=$(openssl rand -hex 32)"
echo "GRAFANA_ADMIN_PASSWORD=$(openssl rand -base64 16)"
8. Deploy the Stack
cd /opt/apix/infra
# Start everything
docker compose --env-file ../.env up -d
# Watch startup logs
docker compose logs -f --tail=50
# Verify all services are healthy
docker compose ps
Expected healthy services after ~60 seconds:
| Service | Health endpoint | Expected |
|---|---|---|
db |
pg_isready |
healthy |
registry |
http://localhost:8180/q/health/live |
{"status":"UP"} |
spider |
http://localhost:8082/q/health/live |
{"status":"UP"} |
portal |
http://localhost:8081/q/health/live |
{"status":"UP"} |
prometheus |
http://localhost:9090/-/healthy |
Prometheus Server is Healthy. |
grafana |
http://localhost:3000/api/health |
{"database":"ok"} |
Quick smoke test (from VPS):
# Registry root (HATEOAS navigation)
curl -s http://localhost:8180/ | python3 -m json.tool
# Metrics endpoint (Prometheus scrape target)
curl -s http://localhost:8180/q/metrics | grep apix_search
# Search endpoint
curl -s "http://localhost:8180/services?capability=nlp" | python3 -m json.tool
Liquibase note
Liquibase runs automatically at startup (quarkus.liquibase.migrate-at-start=true).
If the changelog is missing (db/changelog/db.changelog-master.xml), the registry will
fail to start. Check logs with docker compose logs registry and ensure migrations
are present (WORKLOG Block 1 / C-20 to C-24).
9. Caddy TLS Reverse Proxy
Create infra/Caddyfile:
# infra/Caddyfile
api-index.org {
# Public API — registry
handle /services* { reverse_proxy registry:8180 }
handle /devices* { reverse_proxy registry:8180 }
handle /organizations* { reverse_proxy registry:8180 }
handle /mail-signing-keys { reverse_proxy registry:8180 }
handle / { reverse_proxy registry:8180 }
# Caddy does not forward /q/* to CDN — Quarkus internals only
handle /q/* { reverse_proxy registry:8180 }
# Rate limiting (requires caddy-ratelimit plugin or enterprise)
# Basic protection: Caddy's built-in connection limit
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
}
log {
output file /var/log/caddy/api-index.log
format json
}
}
# Portal — separate subdomain (optional)
portal.api-index.org {
reverse_proxy portal:8081
header Strict-Transport-Security "max-age=31536000; includeSubDomains"
}
# Grafana — restrict to internal access or require basic auth
grafana.api-index.org {
basicauth {
# htpasswd -nb admin <password> — generate and paste hash here
admin $2a$14$REPLACE_WITH_BCRYPT_HASH
}
reverse_proxy grafana:3000
}
Caddy fetches TLS certificates from Let's Encrypt automatically on first request. No manual certificate management needed.
Rebuild the caddy container to pick up the new Caddyfile:
cd /opt/apix/infra
docker compose restart caddy
docker compose logs caddy -f
Verify TLS:
curl -sv https://api-index.org/ 2>&1 | grep -E "SSL|certificate|subject"
10. Bunny.net CDN Setup
The CDN sits in front of Caddy/registry and handles ~95% of read traffic from cache.
One-time setup
cd /opt/apix
# With Loki log forwarding (strongly recommended for observability):
BUNNYNET_API_KEY=your-key \
ORIGIN_URL=https://api-index.org \
CUSTOM_HOSTNAME=api-index.org \
SYSLOG_HOST=<vps-public-ip> \
SYSLOG_PORT=5514 \
./scripts/setup-bunnynet.sh
The script:
- Creates a pull zone pointing at
https://api-index.org - Enables query-string vary cache (so
?capability=nlpand?capability=translationare cached as separate entries — critical for correct cache behavior) - Sets edge TTL to follow origin
Cache-Controlheaders (registry setsmax-age=30on/servicesand/devices;max-age=60on/) - Adds the
api-index.orgcustom hostname - Adds an edge rule to bypass cache for
/q/*(Quarkus health/metrics endpoints) - Enables real-time syslog forwarding to Promtail (when
SYSLOG_HOSTis set) - Prints the CNAME value for your DNS record
Update DNS to point to CDN edge
After the script prints the CDN hostname:
api-index.org CNAME apix-registry.b-cdn.net
Remove the A/AAAA records pointing directly to the VPS. The VPS is now origin-only.
Verify CDN is caching
# First request — cache MISS (origin hit)
curl -sI https://api-index.org/services?capability=nlp | grep -i "cache\|x-cache\|age"
# Second request within 30s — cache HIT
curl -sI https://api-index.org/services?capability=nlp | grep -i "cache\|x-cache\|age"
# From an Asian machine or VPN endpoint (tests geographic edge)
curl -w "Total: %{time_total}s\n" -o /dev/null -s https://api-index.org/services?capability=nlp
# Target: <20ms after warm-up
11. Live Telemetry: Promtail → Loki
Provides real-time telemetry of all CDN traffic (hits + misses) during demos. Origin Prometheus only sees cache misses — Loki sees everything.
Grafana Cloud Loki credentials
In Grafana Cloud (grafana.com):
- Go to your stack → Loki → Details
- Note the Push URL (e.g.
https://logs-prod-eu-west-0.grafana.net/loki/api/v1/push) - Create a service account with
logs:writescope → copy the token
Configure Promtail
# Copy config to system path
sudo cp /opt/apix/scripts/promtail-cdn-logs.yaml /etc/promtail/cdn-logs.yaml
# Fill in credentials
sudo sed -i 's|https://LOKI_PUSH_URL/loki/api/v1/push|https://logs-prod-eu-west-0.grafana.net/loki/api/v1/push|g' \
/etc/promtail/cdn-logs.yaml
sudo sed -i 's|"LOKI_USERNAME"|"123456"|g' /etc/promtail/cdn-logs.yaml # stack user ID
sudo sed -i 's|"LOKI_PASSWORD"|"your-token-here"|g' /etc/promtail/cdn-logs.yaml
Or edit directly: sudo nano /etc/promtail/cdn-logs.yaml — replace the three
LOKI_PUSH_URL, LOKI_USERNAME, LOKI_PASSWORD placeholders.
Create systemd service
sudo tee /etc/systemd/system/promtail.service > /dev/null <<'EOF'
[Unit]
Description=Promtail — Bunny.net CDN log forwarder
After=network.target
[Service]
User=nobody
Group=nogroup
ExecStart=/usr/local/bin/promtail -config.file=/etc/promtail/cdn-logs.yaml
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now promtail
sudo systemctl status promtail
Verify the pipeline end-to-end
# 1. Make a test request through the CDN
curl -s "https://api-index.org/services?capability=nlp" > /dev/null
# 2. Check Promtail received it (should appear within 1-2 seconds)
sudo journalctl -u promtail -f --no-pager
# 3. In Grafana Explore (Loki datasource):
# {job="apix-cdn"} — should show a log line from the test request
12. Grafana Dashboards
Add Loki datasource to Grafana
The stack's provisioned Prometheus datasource is auto-loaded. Add Loki manually or via provisioning:
# infra/grafana/provisioning/datasources/loki.yml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
url: https://logs-prod-eu-west-0.grafana.net
basicAuth: true
basicAuthUser: "123456" # Grafana Cloud stack user ID
secureJsonData:
basicAuthPassword: "your-loki-token"
isDefault: false
editable: false
Restart Grafana to apply:
docker compose restart grafana
Import the OpenClaw demo dashboard
# Copy the dashboard JSON to the provisioning directory
cp /opt/apix/scripts/grafana-demo-dashboard.json \
/opt/apix/infra/grafana/provisioning/dashboards/demo-openclaw.json
Grafana auto-discovers dashboards in the provisioning path (30s poll interval per provider.yml).
No manual import needed.
Alternatively, import via UI:
- Grafana → Dashboards → Import
- Upload
scripts/grafana-demo-dashboard.json - Select the Loki and Prometheus datasources when prompted
Dashboard refresh for demo sessions
The demo dashboard is pre-configured with:
- Refresh: 5 seconds
- Time range: Last 15 minutes
- Auto-play: Enable via Grafana's kiosk mode for the demo screen
Kiosk mode URL (hides nav bar):
https://grafana.api-index.org/d/apix-demo-openclaw/apix-registry-demo?kiosk&refresh=5s
13. Weekly Analytics (Bunny.net Logs)
Bunny.net stores gzip access logs (one file per day). The query-report.sh script
downloads them, parses them, and produces a capability frequency report.
# Basic report — last 7 days
BUNNYNET_API_KEY=your-key \
PULL_ZONE_ID=$(cat /opt/apix/.bunnynet-pull-zone-id) \
./scripts/query-report.sh
With Prometheus Pushgateway (builds a weekly time-series in Grafana):
BUNNYNET_API_KEY=your-key \
PULL_ZONE_ID=$(cat /opt/apix/.bunnynet-pull-zone-id) \
PROMETHEUS_PUSH_URL=https://pushgateway.your-grafana.example/metrics/job/apix-cdn-report \
DAYS=7 \
./scripts/query-report.sh
Weekly cron job (on VPS)
Pass INSTALL_CRON=true when running setup-bunnynet.sh and the cron entry is
installed automatically — no manual crontab -e needed:
BUNNYNET_API_KEY=your-key \
ORIGIN_URL=https://api-index.org \
INSTALL_CRON=true \
./scripts/setup-bunnynet.sh
The script installs a crontab entry for the current user (Mondays 06:00) and
deduplicates on re-run — safe to call again after rotating the API key.
Verify with: crontab -l | grep query-report
The report answers:
- Which capabilities are queried most (all requests, including CDN hits)
- Cache hit ratio per endpoint
- Geographic distribution (PoP breakdown)
- Top query string combinations
14. Verification Checklist
Run through this after each deployment.
Registry
# HATEOAS root
curl -s https://api-index.org/ | python3 -m json.tool
# Search (returns empty array if no services registered yet — correct)
curl -s "https://api-index.org/services?capability=nlp"
# Health
curl -s https://api-index.org/q/health | python3 -m json.tool
# Cache-Control header on search endpoint
curl -sI "https://api-index.org/services?capability=nlp" | grep -i cache-control
# Expected: Cache-Control: public, max-age=30
CDN
# Second request should be a cache HIT
curl -sI "https://api-index.org/services?capability=nlp" | grep -i cache
# Expected: X-Cache: HIT (or similar from Bunny.net)
# Edge latency test — run from a machine outside Germany
curl -w "DNS: %{time_namelookup}s Connect: %{time_connect}s Total: %{time_total}s\n" \
-o /dev/null -s "https://api-index.org/services?capability=nlp"
# Target: Total <20ms from Asia/US after warm-up
Observability
# Prometheus scraping registry
curl -s http://localhost:9090/api/v1/targets | python3 -c \
"import sys,json; [print(t['labels']['job'], t['health']) for t in json.load(sys.stdin)['data']['activeTargets']]"
# Loki receiving CDN logs
# In Grafana Explore: {job="apix-cdn"} | last 5 min should show entries
TLS
curl -sv https://api-index.org/ 2>&1 | grep -E "issuer|subject|expire"
# Should show Let's Encrypt issuer
15. Routine Operations
Update application
cd /opt/apix
# Pull latest code
git pull origin main
# Rebuild affected images
docker build -f infra/Dockerfile.registry -t apix-registry:latest .
# Rolling restart
docker compose up -d registry
docker compose logs registry -f --tail=50
Rotate API key
NEW_KEY=$(openssl rand -hex 32)
# Update .env
sed -i "s/^APIX_API_KEY=.*/APIX_API_KEY=${NEW_KEY}/" /opt/apix/.env
# Restart registry to pick up new key
docker compose up -d registry
echo "New key: ${NEW_KEY}"
# Distribute to all registrar clients before the old key expires
Database backup
# Manual backup
docker exec apix-infra-db-1 pg_dump -U apix apix | gzip > /opt/apix/backups/apix-$(date +%Y%m%d).sql.gz
# Automated daily backup via cron
0 2 * * * docker exec apix-infra-db-1 pg_dump -U apix apix | gzip \
> /opt/apix/backups/apix-$(date +\%Y\%m\%d).sql.gz
# Restore
gunzip -c /opt/apix/backups/apix-20260101.sql.gz | docker exec -i apix-infra-db-1 psql -U apix apix
View logs
# Live registry logs
docker compose -f /opt/apix/infra/docker-compose.yml logs registry -f
# Use the convenience scripts (from project root)
./scripts/logs.sh registry
./scripts/logs.sh spider
./scripts/logs.sh portal
# Promtail (CDN log forwarder)
sudo journalctl -u promtail -f
Purge CDN cache (after deploying schema changes)
curl -sf -X POST "https://api.bunny.net/pullzone/$(cat /opt/apix/.bunnynet-pull-zone-id)/purgeCache" \
-H "AccessKey: ${BUNNYNET_API_KEY}"
Stop / restart the full stack
cd /opt/apix/infra
./scripts/stop.sh # graceful stop
./scripts/restart.sh # stop + start
./scripts/reset.sh # WARNING: drops all volumes including DB data
Appendix: Environment Variable Reference
All variables accepted by the registry container, sourced from .env:
| Variable | Default | Required | Description |
|---|---|---|---|
QUARKUS_DATASOURCE_JDBC_URL |
jdbc:postgresql://db:5432/apix |
yes | Database JDBC URL |
QUARKUS_DATASOURCE_USERNAME |
apix |
yes | DB username |
QUARKUS_DATASOURCE_PASSWORD |
apix |
yes | DB password — use a strong value |
APIX_API_KEY |
dev-insecure-key-change-in-prod |
yes | Write-endpoint auth key |
APIX_REGISTRY_BASE_URL |
http://localhost:8180 |
yes | Used in HATEOAS links |
GLEIF_API_URL |
https://api.gleif.org/api/v1 |
no | O2 verification: GLEIF REST API |
OPENCORPORATES_API_KEY |
(blank) | no | O2 verification: OpenCorporates |
APIX_VERIFICATION_TIMEOUT_MS |
5000 |
no | HTTP timeout for verification calls |
APIX_MAIL_SIGNING_PRIVATE_KEY |
(blank) | no | Ed25519 private key, Base64; ephemeral if blank |
APIX_MAIL_SIGNING_PUBLIC_KEY |
(blank) | no | Ed25519 public key, Base64 |
APIX_MAIL_SIGNING_KID |
dev |
no | Key ID in signed payloads; rotate every 6 months |
SANCTIONS_CACHE_PATH |
./sanctions-cache |
no | Local path for sanctions list cache |
LOG_LEVEL |
INFO |
no | DEBUG / INFO / WARNING / ERROR |