For DevOps & Platform Teams
Alloy is a single binary with embedded migrations, automatic TLS, and a full REST API. No UI servers, no SPA builds, no external migration tools. This guide shows how to deploy Alloy, manage API keys for CI/CD pipelines, set up webhooks for event-driven automation, and monitor service health — all from the command line.
1. Why Alloy for DevOps
Traditional project management tools are SaaS black boxes. You cannot self-host them, you cannot script them reliably, and you cannot integrate them into your infrastructure without fragile browser automation. Alloy is different:
- Single binary, zero runtime dependencies. Download, set env vars, run. No JVM, no Node, no container orchestrator required.
- Embedded migrations. The binary carries its own schema. Set
ALLOY_AUTO_MIGRATE=trueand it migrates on startup — no external migration runner needed. - Automatic TLS. Pass
--tls-domainand Alloy provisions a Let’s Encrypt certificate via ACME. No reverse proxy required. - Dual database backends. SQLite for dev/staging (single file, zero config), PostgreSQL for production (multi-tenant, RLS).
- API-first. Every operation is a REST call. CI scripts, Terraform providers, and monitoring hooks all work natively.
2. Deploying Alloy
Build and run the binary with minimal configuration:
cargo build --release
ALLOY_DATABASE_URL=sqlite://alloy.db \
ALLOY_AUTO_MIGRATE=true \
PORT=3000 \
./target/release/alloy serve
For production with PostgreSQL and TLS:
ALLOY_DATABASE_URL=postgres://alloy:secret@db:5432/alloy \
ALLOY_AUTO_MIGRATE=true \
ALLOY_JWT_PRIVATE_KEY_FILE=/etc/alloy/jwt.pem \
ALLOY_JWT_PUBLIC_KEY_FILE=/etc/alloy/jwt.pub \
ALLOY_TLS_DOMAIN=alloy.example.com \
ALLOY_TLS_CONTACT=ops@example.com \
PORT=443 \
./target/release/alloy serve
Key environment variables for operators:
| Variable | Default | Purpose |
|---|---|---|
ALLOY_DATABASE_URL | sqlite://alloy.db | sqlite://path or postgres://... |
ALLOY_AUTO_MIGRATE | true | Run embedded migrations on startup |
PORT | 3000 | HTTP listen port |
ALLOY_REGISTRATION | open | open or invite |
ALLOY_CORS_ORIGINS | — | Comma-separated allowed origins |
ALLOY_RATE_LIMIT_GLOBAL | — | Requests/min per IP (public endpoints) |
ALLOY_RATE_LIMIT_AUTH | — | Requests/min (authenticated endpoints) |
ALLOY_HTTPS | — | true to set Secure cookie flag |
See the Deployment Guide for the full environment variable reference including S3, Slack, GitHub, and SCIM configuration.
3. Health Checks
Use the unauthenticated /health endpoint for liveness probes,
load balancer checks, and uptime monitors:
curl -s "$BASE_URL/health" | jq .
{
"status": "ok",
"service": "alloy",
"version": "...",
"database": "...",
"db_healthy": true,
"migration_version": "...",
"uptime_seconds": "..."
}
The database field reports the active backend (sqlite or postgresql).
The db_healthy field confirms the database can respond to queries.
The migration_version field shows the latest applied migration number.
The uptime_seconds field shows how long the server has been running.
Wire this into your monitoring:
- Kubernetes:
livenessProbe.httpGet.path: /health - AWS ALB: Target group health check path
/health - Uptime monitors: Alert when
statusis notok
4. API Key Management
API keys let CI/CD pipelines and scripts authenticate without user
credentials. Keys are prefixed alloy_live_ and the raw key is only
shown once at creation time.
Create a key for your CI pipeline:
curl -s -X POST "$BASE_URL/api/v1/api-keys" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "CI Pipeline Key",
"scopes": ["read", "write"]
}' | jq .
{
"id": "...",
"name": "CI Pipeline Key",
"key": "...",
"key_prefix": "...",
"scopes": ["read", "write"],
"project_ids": [],
"created_at": "...",
"expires_at": null
}
Store the key value in your secrets manager immediately — it cannot be
retrieved again. The key authenticates via the same Authorization: Bearer
header as JWTs.
List all keys to audit active credentials:
curl -s "$BASE_URL/api/v1/api-keys" \
-H "Authorization: Bearer $TOKEN" | jq .
[
{
"id": "...",
"name": "CI Pipeline Key",
"key_prefix": "...",
"scopes": ["read", "write"],
"project_ids": [],
"created_at": "...",
"last_used_at": null,
"expires_at": null
}
]
The list response shows key_prefix (first 15 characters) but never the
full key. Use last_used_at to identify stale keys for rotation.
Available scopes:
| Scope | Access |
|---|---|
read | GET endpoints only |
write | GET + POST/PUT/PATCH/DELETE |
admin | Full access (equivalent to read + write) |
Create a read-only key for monitoring dashboards:
curl -s -X POST "$BASE_URL/api/v1/api-keys" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "Monitoring Read-Only",
"scopes": ["read"]
}' | jq .
{
"id": "...",
"name": "Monitoring Read-Only",
"key": "...",
"key_prefix": "...",
"scopes": ["read"],
"project_ids": [],
"created_at": "...",
"expires_at": null
}
Tip: Use
project_idsto restrict a key to specific projects. Pass"project_ids": ["<uuid>"]when creating the key.
5. Revoking API Keys
Revoke a compromised or unused key by deleting it:
curl -s -X DELETE "$BASE_URL/api/v1/api-keys/$API_KEY_ID" \
-H "Authorization: Bearer $TOKEN" -w "%{http_code}" -o /dev/null
Returns 204 No Content on success. The key is immediately invalidated —
any pipeline using it will start receiving 401 Unauthorized.
6. Webhooks for Event-Driven Automation
Webhooks push real-time events to your systems when things happen in Alloy. Use them to trigger CI builds on ticket transitions, post to Slack, or update external dashboards.
Create a webhook to receive ticket events:
curl -s -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/webhooks" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"url": "https://hooks.example.com/alloy",
"event_types": ["ticket.created", "ticket.updated"]
}' | jq .
{
"id": "...",
"org_id": "...",
"url": "https://hooks.example.com/alloy",
"secret": "...",
"event_types": ["ticket.created", "ticket.updated"],
"active": true,
"created_at": "...",
"updated_at": "..."
}
Save the secret — it is only shown at creation time. Use it to verify
webhook signatures via the X-Alloy-Signature header (HMAC-SHA256 of the
request body).
Available event types:
| Event | Fires when |
|---|---|
ticket.created | A new ticket is created |
ticket.updated | A ticket’s fields change |
ticket.status_changed | A ticket transitions status |
comment.created | A comment is added |
sprint.started | A sprint is started |
sprint.completed | A sprint is completed |
List your webhooks:
curl -s "$BASE_URL/api/v1/orgs/$ORG_ID/webhooks" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"items": [
{
"id": "...",
"org_id": "...",
"url": "https://hooks.example.com/alloy",
"event_types": ["ticket.created", "ticket.updated"],
"active": true,
"created_at": "...",
"updated_at": "..."
}
],
"next_cursor": null
}
7. Webhook Delivery Monitoring
Monitor webhook delivery success and debug failures by listing delivery attempts:
curl -s "$BASE_URL/api/v1/webhooks/$WEBHOOK_ID/deliveries" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"items": [],
"next_cursor": null
}
When deliveries exist, each entry includes status (pending, success,
failed), response_status, attempt count, and next_retry_at for
failed deliveries. Alloy retries with exponential backoff: 1 minute,
5 minutes, 25 minutes, ~2 hours, ~10 hours.
Verify webhook signatures in your receiver:
import hmac, hashlib
def verify_signature(secret, body, signature):
expected = hmac.new(
secret.encode(), body.encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
Each delivery includes these headers:
| Header | Content |
|---|---|
X-Alloy-Signature | HMAC-SHA256 hex digest |
X-Alloy-Event | Event type string |
X-Alloy-Delivery | Unique delivery UUID |
8. GitHub Integration
Connect GitHub to Alloy so pull requests automatically transition tickets.
Set ALLOY_GITHUB_WEBHOOK_SECRET on the Alloy server, then configure a
GitHub webhook pointing to:
POST /api/v1/integrations/github/webhook
When a PR branch, title, or body contains a ticket reference like PROJ-42:
- PR opened → ticket transitions to
InReview - PR merged → ticket transitions to
Done - PR closed (not merged) → ticket transitions to
InProgress
No manual status updates needed. Engineers work in Git, Alloy stays in sync.
9. CI/CD Pipeline Recipes
Use API keys in your CI pipelines to automate project management alongside code changes.
Transition a ticket when a deploy succeeds:
TICKET_REF="PROJ-42"
curl -s -X POST "$ALLOY_URL/api/v1/tickets/$TICKET_ID/transition" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ALLOY_API_KEY" \
-d '{"to_status": "Done"}'
Post a deploy comment on a ticket:
curl -s -X POST "$ALLOY_URL/api/v1/tickets/$TICKET_ID/comments" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ALLOY_API_KEY" \
-d "{\"body\": \"Deployed to production via CI at $(date -u +%Y-%m-%dT%H:%M:%SZ)\"}"
Generate a weekly capitalization report for finance:
curl -s "$ALLOY_URL/api/v1/reports/capitalization?period=2026-03&group_by=project" \
-H "Authorization: Bearer $ALLOY_API_KEY" -o cap-report.json
These recipes use plain code blocks because they contain shell variables not set in the verification environment.
10. Getting Started — Day 1 Checklist
Here is what to do when setting up Alloy for your platform:
- Deploy the binary — Build with
cargo build --release, setALLOY_DATABASE_URLandPORT, runalloy serve - Verify health —
curl /healthreturns{"status": "ok"} - Generate JWT keys — Create Ed25519 keypair, set
ALLOY_JWT_PRIVATE_KEY_FILEandALLOY_JWT_PUBLIC_KEY_FILE - Enable TLS — Set
--tls-domainand--tls-contactfor automatic Let’s Encrypt provisioning - Create CI API keys —
POST /api/v1/api-keyswith appropriate scopes, store in your secrets manager - Set up webhooks —
POST /api/v1/orgs/{org_id}/webhooksfor ticket events your pipelines care about - Configure GitHub integration — Set
ALLOY_GITHUB_WEBHOOK_SECRETand create a GitHub webhook pointing to Alloy - Add monitoring — Wire
/healthinto your uptime monitor and alerting system - Set rate limits — Configure
ALLOY_RATE_LIMIT_GLOBALandALLOY_RATE_LIMIT_AUTHfor your traffic patterns - Back up your database — For SQLite: copy the
.dbfile. For PostgreSQL: usepg_dump
Each step links to its respective section above. Start with steps 1–3 and iterate from there.
Further reading:
- Deployment Guide — full environment variable reference
- API Reference — complete endpoint documentation
- Teams, Roles & Permissions — access control
- API Automation Tutorial — scripting patterns
- MCP Tools Reference — MCP automation