Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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=true and it migrates on startup — no external migration runner needed.
  • Automatic TLS. Pass --tls-domain and 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:

VariableDefaultPurpose
ALLOY_DATABASE_URLsqlite://alloy.dbsqlite://path or postgres://...
ALLOY_AUTO_MIGRATEtrueRun embedded migrations on startup
PORT3000HTTP listen port
ALLOY_REGISTRATIONopenopen or invite
ALLOY_CORS_ORIGINSComma-separated allowed origins
ALLOY_RATE_LIMIT_GLOBALRequests/min per IP (public endpoints)
ALLOY_RATE_LIMIT_AUTHRequests/min (authenticated endpoints)
ALLOY_HTTPStrue 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 status is not ok

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:

ScopeAccess
readGET endpoints only
writeGET + POST/PUT/PATCH/DELETE
adminFull 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_ids to 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:

EventFires when
ticket.createdA new ticket is created
ticket.updatedA ticket’s fields change
ticket.status_changedA ticket transitions status
comment.createdA comment is added
sprint.startedA sprint is started
sprint.completedA 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:

HeaderContent
X-Alloy-SignatureHMAC-SHA256 hex digest
X-Alloy-EventEvent type string
X-Alloy-DeliveryUnique 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:

  1. Deploy the binary — Build with cargo build --release, set ALLOY_DATABASE_URL and PORT, run alloy serve
  2. Verify healthcurl /health returns {"status": "ok"}
  3. Generate JWT keys — Create Ed25519 keypair, set ALLOY_JWT_PRIVATE_KEY_FILE and ALLOY_JWT_PUBLIC_KEY_FILE
  4. Enable TLS — Set --tls-domain and --tls-contact for automatic Let’s Encrypt provisioning
  5. Create CI API keysPOST /api/v1/api-keys with appropriate scopes, store in your secrets manager
  6. Set up webhooksPOST /api/v1/orgs/{org_id}/webhooks for ticket events your pipelines care about
  7. Configure GitHub integration — Set ALLOY_GITHUB_WEBHOOK_SECRET and create a GitHub webhook pointing to Alloy
  8. Add monitoring — Wire /health into your uptime monitor and alerting system
  9. Set rate limits — Configure ALLOY_RATE_LIMIT_GLOBAL and ALLOY_RATE_LIMIT_AUTH for your traffic patterns
  10. Back up your database — For SQLite: copy the .db file. For PostgreSQL: use pg_dump

Each step links to its respective section above. Start with steps 1–3 and iterate from there.


Further reading: