Self-host on AWS

Sigbound runs in your own AWS account from the same Terraform that runs the hosted service. Nothing is built on your machine: the stack pulls published, versioned images.

What you get

The whole product in one account and one region: the forge, the gate, CI on scale-to-zero hosts, Sigbound AI on your own model key, and every merge signed with a key that never leaves the account. The Terraform lives in deploy/ of the source repository and is engine-neutral: Terraform or OpenTofu.

Prerequisites

  • An AWS account with administrator access, and the AWS CLI signed in to it (aws sts get-caller-identity answers).
  • Terraform 1.10 or newer, or OpenTofu (TF=tofu).
  • A domain with a Route53 hosted zone in the same account. The stack issues the certificate and the records. Without a domain the stack runs on AWS-given addresses, and sign-in mail stays off.
  • An Anthropic API key, if you want Sigbound AI reads.
  • Docker is not needed. An install pulls published images.

1. Bootstrap, once per account

The bootstrap layer creates the Terraform state bucket and the image repositories. It is applied once and left alone afterwards, so a stack can be destroyed and rebuilt without eating its own state.

cd deploy/bootstrap && terraform init && terraform apply

2. Install

cd deploy
make install TF=tofu VERSION=v0.1.0 \
  DOMAIN=forge.example.com BUDGET_EMAIL=ops@example.com \
  CI_HOSTS=true PLATFORM_AI=true

VERSION names the published release; DOMAIN is the apex or subdomain whose zone you own; BUDGET_EMAIL receives the budget alarm, the operational alarms, and mail sent to the domain. CI_HOSTS=true provisions the CI host tier; CI_LAMBDA=true adds the sandbox-per-run lane. Leave INVITE_ONLY at its default, false, unless you want to gate sign-up by hand. make info prints the addresses at the end.

3. Mail

The stack verifies the domain with SES and sends as noreply@<domain>: sign-in codes, invitations and notifications. A new AWS account starts in the SES sandbox, which delivers only to verified addresses; request production access once.

aws sesv2 put-account-details --production-access-enabled --mail-type TRANSACTIONAL \
  --website-url https://<domain> \
  --use-case-description "Transactional mail for an internal code forge: sign-in codes, invitations, notifications."

Mail to any address at the domain is received by SES, kept in the mail bucket for 90 days, and forwarded to BUDGET_EMAIL.

4. Sigbound AI

The model key goes into Parameter Store as a SecureString; Terraform never reads it. The key is read from standard input so it never lands in a shell history.

make model-key < key.txt
# stored as /sigbound/PLATFORM_ANTHROPIC_API_KEY

Then install with PLATFORM_AI=true, or re-run the install with it. The runner's reader class is provisioned only when the flag is set.

5. First sign-in

Open https://<domain>/signup and create the first account with a password, or sign in with a one-time code. The first account creates the first space; invite the rest from the space's Members page.

Variables

The Makefile turns its flags into Terraform variables. The ones an install sets:

VariableMeaningDefault
image_registryWhere the published images live. A fork points this at its own registry.public.ecr.aws/w4g9d9z4/sigbound
sigbound_versionThe release to run: the tag of the published app and runner images. make install VERSION= sets it.v0.1.0
platform_aiProvision Sigbound AI's reader class. Needs the model key in Parameter Store.false
invite_onlyAccounts are created by invitation only. Off for a private installation whose sign-up is already inside the walls.false
db_snapshotAn encrypted snapshot to restore the database from, for the one apply that replaces it. Empty afterwards.empty
domainThe apex domain with an existing Route53 zone. Empty runs on AWS-given addresses.empty
budget_emailWhere the budget alarm lands. Required.none
ci_hosts_enabledCI jobs on EC2 hosts in container mode: a scale-to-zero group with a warm pool. CI_HOSTS=true.false
ci_lambda_enabledFitting CI jobs in Lambda microVMs, one per run. CI_LAMBDA=true.false
protectProduction posture: database deletion protection and a final snapshot. PROTECT=false for a stack that should die clean.true
regionThe one region the stack lives in. REGION=.us-east-1

What is in the stack

  • A VPC across two availability zones, an application load balancer with an ACM certificate, and CloudFront in front of the web assets.
  • ECS Fargate for the application and the runner tier; an EC2 auto-scaling group for CI hosts when enabled; two Lambda functions (the mail forwarder, and the CI lane when enabled).
  • RDS PostgreSQL, multi-AZ, encrypted at rest, with seven days of automated backups and point-in-time recovery.
  • S3 buckets for repository objects (versioned), web assets, received mail, and the CloudTrail log, blocked from public access.
  • SES for sending and receiving mail on the domain, and a Cognito user pool that holds password credentials for password accounts.
  • Parameter Store for the secrets the tasks read at boot; CloudWatch logs and alarms; CloudTrail; an AWS budget with an alarm to BUDGET_EMAIL.

Upgrading

Releases are tagged in the repository and published as images under the same tag. Database migrations run when the application boots, so a fresh stack schemas itself and an upgraded one moves forward. Read the release notes before moving more than one minor version.

make install VERSION=v0.2.0 ...same flags...

Building your own images

A fork that changes the code publishes its own images and points image_registry at them. make release builds the four images from the tree and pushes them under VERSION to PUBLIC_REGISTRY. make apply TAG=<short sha> applies the stack against images already pushed to the account's own ECR under a commit tag, without rebuilding them.

make release VERSION=v0.1.0 PUBLIC_REGISTRY=public.ecr.aws/<alias>/sigbound BUDGET_EMAIL=ops@example.com
make apply TAG=6356fea DOMAIN=forge.example.com BUDGET_EMAIL=ops@example.com

Tearing down

make destroy lifts the deletion guards, destroys the stack, then asks AWS what is left and prints a removal command for each survivor. The final database snapshot still follows protect. The bootstrap layer is left alone.

make destroy TF=tofu DOMAIN=forge.example.com BUDGET_EMAIL=ops@example.com

What is not yet possible

  • More than one region: the stack is single-region.
  • Running outside AWS. The application depends on object storage, a database and a container runtime, and a cloud-agnostic package is planned after this one.
  • Google sign-in from the stack alone: creating the OAuth client is a Google Cloud console step, so its two variables are set by hand on the service when that client exists. The button stays hidden until then.