Self-Hosted Installation
Install AGLedger on your own infrastructure using Docker Compose, Kubernetes (Helm), or an air-gap bundle. This guide covers installation only. For post-install account provisioning, see the Onboarding Guide. For YAML-based bulk provisioning, see the YAML Provisioning Guide.
1. Prerequisites
| Requirement | Minimum | Recommended (production) |
|-------------|---------|--------------------------|
| Docker Engine | 24.0+ | Latest stable |
| Docker Compose | v2 | Latest stable |
| RAM | 4 GB | 8 GB |
| CPU | 2 cores | 4 cores |
| Disk | 20 GB free | Scales with usage |
| CLI tools | jq, openssl | |
ECR access. AGLedger images are hosted in a private Amazon ECR registry. You need AWS credentials with these IAM permissions:
ecr:GetAuthorizationToken(on*)ecr:BatchGetImage(on the AGLedger repository)ecr:GetDownloadUrlForLayer(on the AGLedger repository)
Authenticate before pulling images:
aws ecr get-login-password --region us-west-2 \
| docker login --username AWS --password-stdin \
705542379002.dkr.ecr.us-west-2.amazonaws.com
If you cannot reach ECR (air-gapped network), skip to Section 6: Air-Gap Deployment.
2. Quick Install (Docker Compose)
git clone https://github.com/agledger-ai/self-hosted.git
cd self-hosted
./install.sh
The installer is non-destructive. If .env already exists, it skips secret generation and reuses it. To start fresh, delete docker-compose/.env and re-run.
What install.sh does
| Step | Action |
|------|--------|
| 1 | Checks prerequisites (Docker, Compose, jq, openssl, RAM, CPU) |
| 2 | Authenticates with ECR (warns but continues if aws CLI is absent) |
| 3 | Generates secrets and writes docker-compose/.env |
| 4 | Detects database mode (bundled or external) |
| 5 | Pulls images, starts PostgreSQL (if bundled), runs migrations |
| 6 | Creates the platform API key and saves it to .env |
Generated secrets
The installer generates three secrets locally. None leave your environment.
| Secret | Format | Purpose |
|--------|--------|---------|
| API_KEY_SECRET | 64-character hex string | HMAC-SHA256 key for API key hashing |
| VAULT_SIGNING_KEY | Base64 PKCS#8 DER | Ed25519 private key for audit vault signatures |
| POSTGRES_PASSWORD | 32-character alphanumeric | Bundled PostgreSQL password (ignored with external DB) |
The VAULT_SIGNING_KEY is generated by running the AGLedger image itself (generate-signing-key.js), so the image must be pullable before this step completes.
Platform API key
At the end of installation, the installer prints a platform API key (prefixed ach_pla_). This key has full admin access. Save it immediately — it is shown only once. The key is also written to docker-compose/.env as PLATFORM_API_KEY.
If you lose the platform key, regenerate it:
./scripts/reset-platform-key.sh
Installer flags
./install.sh --version 0.15.6 # Pin a specific version
./install.sh --external-db # Skip bundled PostgreSQL
./install.sh --non-interactive # No prompts (CI/automation)
./install.sh --with-monitoring # Enable Jaeger, Prometheus, Grafana
Bundled vs. external database
The installer auto-detects which mode to use:
- Bundled PostgreSQL (default). If
DATABASE_URLis not set in.env, the installer starts a PostgreSQL 18 container alongside AGLedger. Suitable for evaluation and small deployments. - External database. If
DATABASE_URLis set in.env(or--external-dbis passed), the installer skips the bundled PostgreSQL container. See Section 3 for requirements.
Verifying the installation
After the installer completes, the API is available at http://localhost:3001:
curl http://localhost:3001/health
# {"status":"ok"}
curl http://localhost:3001/health/ready
# {"status":"ok","checks":{"database":"ok","worker":"ok"}}
Swagger UI is served at http://localhost:3001/docs.
3. External Database Setup
For production, use a managed PostgreSQL service: Aurora PostgreSQL, RDS, Cloud SQL, or Azure Flexible Server.
Connection string
Set DATABASE_URL in docker-compose/.env before running install.sh:
DATABASE_URL=postgresql://agledger:<PASSWORD>@your-cluster.cluster-xxx.us-west-2.rds.amazonaws.com:5432/agledger?sslmode=require
Connection pooler warning
Do not place a connection pooler in transaction mode between AGLedger and PostgreSQL. This includes:
- AWS RDS Proxy
- PgBouncer in transaction mode
- Cloud SQL managed pooling
- Azure built-in PgBouncer
AGLedger uses pg-boss for background job processing, which requires LISTEN/NOTIFY. Transaction-mode poolers silently drop these notifications, causing jobs to stall without errors.
Use direct connections only.
Role separation
For tighter database security, use two connection strings:
| Variable | Role | Privileges |
|----------|------|-----------|
| DATABASE_URL | agledger_app | DML only (SELECT, INSERT, UPDATE, DELETE) |
| DATABASE_URL_MIGRATE | Owner / postgres | DDL (CREATE, ALTER, DROP) for schema migrations |
When DATABASE_URL_MIGRATE is set, the migration runner uses it instead of DATABASE_URL. If unset, both operations use DATABASE_URL (which must then have DDL privileges).
Pool sizing
Adjust DATABASE_POOL_MAX based on your database's connection limits. AGLedger's total connection usage is approximately pool * 2 + 5 (API + Worker + pg-boss overhead).
| Database | Max connections | Recommended DATABASE_POOL_MAX |
|----------|----------------|----------------------------------|
| Aurora Serverless 0.5 ACU | ~90 | 10 |
| Aurora Serverless 2 ACU | ~50 | 15 |
| RDS db.t3.medium (4 GB) | ~420 | 20 (default) |
SSL certificates
The AGLedger Docker image bundles the AWS RDS/Aurora global CA bundle at /etc/ssl/certs/rds-global-bundle.pem. To enable sslmode=verify-full with Aurora or RDS, set:
NODE_EXTRA_CA_CERTS=/etc/ssl/certs/rds-global-bundle.pem
For Cloud SQL or private CAs, mount your certificate into the container and override the path:
NODE_EXTRA_CA_CERTS=/certs/your-ca.pem
For the bundled PostgreSQL (no TLS), set ALLOW_DB_WITHOUT_SSL=true.
4. Production Hardening
Production compose overlay
Apply docker-compose.prod.yml for production deployments. It adds restart policies, read-only filesystems, resource limits, and log rotation.
With bundled PostgreSQL:
cd docker-compose
docker compose \
-f docker-compose.yml \
-f docker-compose.postgres.yml \
-f docker-compose.prod.yml \
up -d --wait
With external database:
cd docker-compose
docker compose \
-f docker-compose.yml \
-f docker-compose.prod.yml \
up -d --wait
The production overlay applies the following to the API and Worker containers:
| Setting | Value |
|---------|-------|
| Restart policy | unless-stopped |
| Filesystem | Read-only (read_only: true) |
| Temp space | tmpfs at /tmp (64 MB) |
| CPU limit | 1.0 core |
| Memory limit | 512 MB |
| Memory reservation | 256 MB |
| Log rotation | json-file driver, 50 MB per file, 5 files max |
Reverse proxy
In production, place AGLedger behind a TLS-terminating reverse proxy. Set TRUST_PROXY=true in .env so AGLedger reads X-Forwarded-For and X-Forwarded-Proto headers correctly.
Caddy (automatic HTTPS via Let's Encrypt):
agledger.example.com {
reverse_proxy localhost:3001
request_body {
max_size 1MiB
}
header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy strict-origin-when-cross-origin
-Server
}
}
nginx:
upstream agledger {
server 127.0.0.1:3001;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name agledger.example.com;
ssl_certificate /etc/ssl/certs/agledger.crt;
ssl_certificate_key /etc/ssl/private/agledger.key;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 1m;
location / {
proxy_pass http://agledger;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_connect_timeout 10s;
proxy_read_timeout 35s;
proxy_send_timeout 10s;
}
}
Full example configurations are in examples/reverse-proxy/ in the self-hosted repository.
Port bindings
All Docker Compose service ports bind to 127.0.0.1 (localhost only) by default:
| Service | Container port | Host port | |---------|---------------|-----------| | API | 3000 | 3001 | | Worker health | 3001 | 3002 | | PostgreSQL (bundled) | 5432 | 5432 |
5. Kubernetes / Helm Install
Basic install
helm install agledger helm/agledger/ \
--namespace agledger \
--create-namespace \
--set database.externalUrl="postgresql://agledger:<PASSWORD>@your-db-host:5432/agledger?sslmode=require" \
--set secrets.apiKeySecret="<YOUR_64_CHAR_HEX>" \
--set secrets.vaultSigningKey="<YOUR_BASE64_ED25519_KEY>" \
--set secrets.licenseKey="<YOUR_LICENSE_PEM>"
Generate the required secrets before installing:
# API_KEY_SECRET (64-char hex)
openssl rand -hex 32
# VAULT_SIGNING_KEY (Ed25519 — use the AGLedger image)
docker run --rm \
705542379002.dkr.ecr.us-west-2.amazonaws.com/agledger-ai/self-hosted:<version> \
dist/scripts/generate-signing-key.js
Using an existing Kubernetes Secret
Instead of passing secrets inline, create a Secret and reference it:
secrets:
existingSecret: agledger-secrets # Name of your pre-created Secret
The Secret must contain these keys:
| Key | Required |
|-----|----------|
| DATABASE_URL | Yes |
| API_KEY_SECRET | Yes |
| VAULT_SIGNING_KEY | Yes |
| WEBHOOK_ENCRYPTION_KEY | No |
| DATABASE_URL_MIGRATE | No |
| AGLEDGER_LICENSE_KEY | No |
Production values
Apply values-production.yaml for production deployments:
helm install agledger helm/agledger/ \
--namespace agledger \
--create-namespace \
--values helm/agledger/values-production.yaml \
--values your-secrets.yaml
The production preset configures:
| Setting | Value |
|---------|-------|
| API replicas | 2 |
| Worker replicas | 2 |
| HPA | Enabled (2-10 replicas, 80% CPU target) |
| PodDisruptionBudget | minAvailable: 1 |
| Topology spread | 1 pod per availability zone |
| API memory | 512 Mi request / 1 Gi limit |
| Worker autoscaling | Enabled (2-10 replicas) |
| Network policy | Enabled |
| Trust proxy | true |
| Log level | warn |
License delivery
Two options for providing an enterprise license on Kubernetes:
Option A: Inline in Secret (simpler)
secrets:
licenseKey: |
-----BEGIN LICENSE FILE-----
<your license PEM content>
-----END LICENSE FILE-----
Option B: Mounted file (preferred for key management)
Create a Kubernetes Secret containing the license PEM file, then reference it:
license:
keyFile:
enabled: true
secretName: agledger-license # K8s Secret name
secretKey: license.pem # Key within the Secret
mountPath: /run/secrets/agledger-license.pem
If no license is configured, AGLedger runs in Starter mode. All core features are enabled. License problems fail open — the server always starts.
Bundled PostgreSQL (dev/CI only)
The Helm chart can deploy a bundled PostgreSQL for development or CI. This uses an ephemeral emptyDir volume — data does not survive pod restarts.
postgres:
bundled:
enabled: true
database:
externalUrl: "" # Leave empty when using bundled postgres
Do not use bundled PostgreSQL in production.
Ingress
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: api.agledger.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: agledger-tls
hosts:
- api.agledger.example.com
Set config.trustProxy: true when using an ingress controller.
EKS image pull
EKS nodes pull from ECR via their IAM instance role. Attach the AmazonEC2ContainerRegistryReadOnly managed policy to the node IAM role. No imagePullSecrets are needed.
6. Air-Gap Deployment
For networks with no internet access, create an air-gap bundle on a connected machine and transfer it to the target host.
Create the bundle
On a machine with Docker and internet access:
./scripts/airgap-bundle.sh 0.15.6
This pulls all required images (AGLedger, PostgreSQL, and optional monitoring stack), saves them as tarballs, copies all deployment files, generates SHA256 checksums, and packages everything into a single archive:
agledger-airgap-0.15.6.tar.gz
Deploy on the air-gapped host
Transfer the archive to the target machine, then follow these steps:
# 1. Extract the bundle
tar -xzf agledger-airgap-0.15.6.tar.gz
cd agledger-airgap-0.15.6
# 2. Verify checksums (optional but recommended)
sha256sum -c SHA256SUMS
# 3. Load Docker images
./load-images.sh
# 4. Copy the environment template
cp docker-compose/.env.example docker-compose/.env
# 5. Edit .env — set your secrets (API_KEY_SECRET, VAULT_SIGNING_KEY)
# Generate API_KEY_SECRET on the air-gapped host:
# openssl rand -hex 32
# For VAULT_SIGNING_KEY, generate before bundling or use:
# docker run --rm <image> dist/scripts/generate-signing-key.js
# 6. Set file permissions
chmod 600 docker-compose/.env
# 7. Start services
cd docker-compose
docker compose \
-f docker-compose.yml \
-f docker-compose.postgres.yml \
up -d --wait
The bundle includes the Helm chart at helm/agledger/ for Kubernetes deployments.
7. Post-Install Verification
Preflight checks
Run the built-in preflight script to verify database connectivity, migrations, and configuration:
./scripts/preflight.sh
If the API container is running, preflight executes inside it. Otherwise, it starts a one-off container.
Health endpoints
| Endpoint | Purpose |
|----------|---------|
| GET /health | Basic liveness check. Returns {"status":"ok"} if the process is running. |
| GET /health/ready | Readiness check. Verifies database connectivity and worker health. |
| GET /conformance | Returns supported API version and feature set. |
# Liveness
curl -s http://localhost:3001/health | jq .
# Readiness (database + worker)
curl -s http://localhost:3001/health/ready | jq .
Smoke test
The self-hosted repository includes a smoke test that validates health, conformance, and optionally runs a full mandate lifecycle:
# Health checks only
./tests/smoke-test.sh http://localhost:3001
# Full lifecycle (reads PLATFORM_API_KEY from .env automatically)
./tests/smoke-test.sh http://localhost:3001
8. Next Steps
- Provision your first enterprise account. Follow the Self-Hosted Onboarding Guide to create an enterprise, register agents, and run your first mandate.
- Bulk provisioning. Use the YAML Provisioning Guide to provision enterprises, agents, and contract schemas from a single YAML file.
- Upgrade. Run
./upgrade.sh <version>to upgrade to a new release. See the Operations Guide for details. - Backup and restore. See the Operations Guide for PostgreSQL backup procedures.
- Configuration. See the Configuration Reference for all environment variables, licensing, and Helm chart values.
Validated against AGLedger self-hosted v0.15.6. Source: self-hosted.