Home Quick start The read The build Deploy CI/CD Changelog v1.2.0 Start free

Installation

There is nothing to install. Bronco runs through npx on Node 18 or newer, on Windows, macOS and Linux alike — no Terraform and no AWS CLI on your machine.

bash
npx bronco-cli doctor            # the free read, no license, nothing uploaded
npx bronco-cli --version

Pinning a version

Anything that runs unattended — CI, a scheduled job, an agent — should name the version, so publishing a Bronco release never changes what your pipeline runs. bronco ci generate writes it pinned for you.

bash
npx bronco-cli@1.2.0 deploy --env prod

The command is bronco

The package npm serves is bronco-cli; the command it installs is bronco. Install it once and the shorter name is the one you type.

bash
npm install -g bronco-cli
bronco doctor
bronco activate <your-license-key>

License Activation

💡

Pro-tip — no key needed to start. Read-only diagnostics are 100% free and require no license: bronco doctor, bronco cost, bronco audit and bronco risk work fully out of the box. A license key is only required when you're ready to provision or manage infrastructure — forge, apply, deploy, operate and watch on. Came in for the free npx bronco-cli doctor? You can ignore everything on this page until you decide to build.

Windows and macOS installers prompt for the key automatically. On Linux, or if you skip it during install:

bash
# Activate
bronco activate BRNCO-XXXX-XXXX-XXXX

# Check status
bronco license

# Release a slot (before switching machines)
bronco deactivate

Your machine seats depend on your plan — Free activates on 1 machine, Developer on 2 (your laptop + CI), Agency on 10. Deactivate before moving to a new machine to free a slot.

Quick Start

Two paths. Path A costs nothing and needs no install; Path B builds real AWS infrastructure and starts a real bill.

Path A — Read an account you already have (free)

No licence, no install, no provisioning, and nothing is written to AWS.

  1. Run npx bronco-cli doctor Bronco connects with your existing AWS credentials and runs the full diagnose loop — spend & waste, security exposure, reliability risk — in one read-only pass. It never writes anything.
  2. Read what it found — and what it could not You finish with one summary: what you pay, a security grade and a reliability grade, each with the number of checks it was scored over and the named finding that would move it — plus the checks that could not run and the permission each one needs. A denied read is reported as a coverage gap, never as a pass.
  3. Act on the biggest win Follow the recommended next step — usually bronco fix --dry-run to preview safe, reversible fixes. Nothing is applied until you say so.

Path B — Build a hardened stack & deploy your app (~15 minutes)

From zero to a live AWS stack with your application running.

🧱

Two commands, two jobs — don't confuse them. bronco forge is The Foundation: it provisions your network, databases, cache and container cluster — run it once per environment (or when the infrastructure changes). bronco deploy is The Application: it packs and ships your app containers from bronco.app.yaml — run it on every commit. You forge the ground once; you deploy onto it for the life of the project.

  1. Install & activate Run the installer for your OS and paste your license key when prompted. (Provisioning is the paid half — the diagnose loop above stays free forever.)
  2. Run bronco forge Have a conversation: which shared resources do you need (database, cache, storage), and what are you deploying on top of them? Bronco provisions exactly that — nothing speculative — in about 8–10 minutes, in your own account.
  3. bronco.app.yaml is already there If you told bronco forge about your services, it wrote a complete deployment contract from your answers — Dockerfile paths, secrets, schedules, all of it. Nothing left to fill in by hand.
  4. Run bronco deploy Bronco uploads secrets, builds and pushes Docker images, runs migrations, rolls out to ECS, and syncs your frontend. No bronco.app.yaml yet? Bronco has the same conversation on the spot, provisions what's missing, and deploys — no wall, no error message.
  5. Run bronco ci generate Generates a ready-to-use pipeline YAML for GitHub Actions, Azure Pipelines, GitLab CI or Bitbucket. Push to main and your pipeline fires automatically — then turn on bronco watch so it never silently drifts or overspends again.

bronco doctor

The 10-minute Cloud Doctor — start here. One command runs the entire diagnose loop on any AWS account in a single read-only pass: real spend & waste, security exposure, reliability risk. It's the fastest way to prove Bronco's value before you commit to anything — free, read-only, and it never writes to AWS.

npx bronco-cli doctor

You finish with one compact summary. Every grade carries the number of checks it was scored over and the named finding that would move it, and the run states what it could not read — a denied read is a coverage gap, never a pass:

the shape of the answer — your own numbers will differ
Bronco found:
  <$>/mo   recoverable, each finding priced
  <n>      high/critical security findings
  <n>      reliability risks
  <n>      of them carry a reversible fix
  <n>      checks could not run, and which permission each needs

Next safest step:
  bronco fix --dry-run

Every run ends by pointing at the single safest next step, so you're never left wondering what to do with the findings. Want the detail behind each line? That's exactly what cost, audit and risk below are for — and they're free to preview too.

💡

Why this is the one to run first: no install, no licence, no Terraform, no workspace, and nothing is written to AWS. It reads an account Bronco never built, so there is nothing to set up before it can answer.

What happens when one resource out of many is unreadable

A check that touches twelve buckets and is denied one of them has two options, and only one of them is honest. Every check in the catalogue was read and classified for this, and twenty-six were throwing away the whole account’s answer over a single denial — on one real account the two S3 checks returned 17 findings, and with read access to one bucket out of twelve denied they returned none, though fifteen of the seventeen were about buckets that read perfectly well.

Today 27 of the 58 checks report what they could read and name what they could not. The rest refuse to answer partially on purpose: there a missing resource changes the answer rather than shortening it, and “nobody in this account holds unrestricted access” is not a sentence to make over a list that quietly lost someone. Either way the run states the gap and the permission it needs, so a denied read is never scored as a pass.

bronco cost

Real AWS spend and waste detection — on any account. bronco cost is agnostic and read-only: point it at any AWS account (one Bronco never built) and it reads your actual spend from Cost Explorer and finds money you're burning on nothing. No workspace, no setup. This is the "where exactly is the money going?" zoom-in on your doctor receipt.

npx bronco-cli cost

It reports the last 30 days of spend broken down by service, then the waste it found with a potential monthly savings figure:

The free preview shows the headline spend, your top services, and the top waste items. Add --full (license required) for every itemized line, and --all-regions to sweep every enabled region instead of just the active one.

Cost Explorer must be enabled on the account (it is for any account that's opened the Billing console). If it isn't, Bronco still runs the waste checks and notes the spend as unavailable.

bronco audit

Security-posture scan — on any account. Read-only by default; finds the exposures that matter and scores your posture 0–100. One open port to 0.0.0.0/0 is cheaper to find here than in an incident report — and Bronco will close it for you in the same command.

npx bronco-cli audit

Checks include: S3 buckets that are public or missing Block Public Access; security groups open to 0.0.0.0/0 on sensitive ports (SSH, RDP, databases); the root account without MFA; publicly accessible or unencrypted RDS; unencrypted EBS; IAM users without MFA. Each finding carries the resource, why it matters, and how to fix it — grouped by severity.

Interactive micro-fixes

Some findings are auto-fixable. Run with --fix (license required) and Bronco walks each one, applying only the ones you approve:

# ? Revoke SSH (22) from 0.0.0.0/0 on sg-0abc123? (y/N)
# ? Enable Block Public Access on my-bucket? (y/N)

Fixes are the only writes audit ever makes — each is explicit and opt-in.

bronco risk

Production-readiness & reliability — on any account. Scores how ready your infrastructure is for a bad day and lists the gaps, framed as "what breaks if X":

npx bronco-cli risk

Same model as the others: free preview, --full for everything (license), --all-regions for a full sweep. The cheapest backup is the one you set up before you needed it — this is the command that tells you which one you're missing.

bronco login

Connect one or more AWS accounts up front — paste a key once, give it a name, and Bronco remembers it. No need to run init just to point Bronco at a new account, and no juggling environment variables to switch between a client's account and your own.
ℹ️

Already using aws configure, AWS SSO, or AWS_* environment variables? You don't need this — Bronco picks those up automatically. bronco login is for people who'd rather paste a key once and have Bronco manage it, or who work across several accounts.

bash
# Connect an account — names it, verifies the key with AWS, makes it active
bronco login --aws

# Switch the active account without re-entering keys
bronco login --use acme-dev

What it asks: a short name for the account (e.g. acme-prod), the default region, and an AWS access key ID + secret. Bronco verifies the key against STS before saving — a bad key is caught immediately, not three steps into a deploy.

Where it's stored: each account becomes its own named profile in ~/.aws/credentials ([bronco-<name>]) — the standard AWS CLI file, with 0600 permissions. Bronco never writes to [default] or any profile you already have. A small index at ~/.bronco/accounts.json tracks the names and which one is active (no secrets live there).

💡

No access key yet? bronco login (and init/deploy) can open a pre-filled AWS CloudFormation page that creates a scoped bronco-deployer IAM user for you — one click, exactly the permissions Bronco needs, no admin key ever pasted.

bronco accounts

List every connected account and choose which one is active. The active account is the one init, deploy, plan — everything — runs against.

bash
bronco accounts                   # list accounts, pick the active one interactively
bronco accounts --use acme-prod   # switch directly, no prompt

# Example output:
   acme-prod   (active)
     account 111122223333 · us-east-1 · [bronco-acme-prod]
  ○ acme-dev
     account 444455556666 · us-west-2 · [bronco-acme-dev]
ℹ️

An explicit per-command override always wins over the active account: run with AWS_PROFILE=… or AWS_ACCESS_KEY_ID=… set and Bronco uses exactly that, untouched — so CI runners and one-off AWS_PROFILE=… bronco deploy are never silently redirected.

bronco logout

Forget a connected account — deletes its key from ~/.aws/credentials and removes it from the index. The IAM user in AWS is left untouched; this only clears the local copy. If you forget the active account, Bronco hands "active" to one of the remaining accounts.

bash
bronco logout              # pick which account to forget, interactively
bronco logout acme-dev     # forget a specific account

bronco forge — The Foundation

Run this once per environment (or when the infrastructure itself changes). bronco forge is a conversation that ends with a live AWS stack — your network (VPC), secure databases (RDS), cache (Redis) and container cluster (ECS Fargate) — and, if you have apps to run, a complete bronco.app.yaml ready to deploy. It provisions the ground; bronco deploy ships your code onto it. (Aliased as bronco init — older scripts and pipelines keep working.)

bash
bronco forge [--dry-run]
FlagDescription
--dry-runPreview the plan locally. The workspace it writes is finished and deliberately unusable — it has no state backend, so no later command can apply it. To build for real, run bronco forge again with the same answers.

How the conversation flows:

What it creates: VPC with public/private/database subnets · NAT Gateway · ECS Fargate cluster · ECR repositories · ALB with path routing (only when a service is public) · Secrets Manager + KMS · CloudWatch dashboard + alarms · S3 remote state bucket — plus, only if you said yes to them: RDS PostgreSQL, ElastiCache Redis, CloudFront + S3 for static assets, a Cognito user pool, and a verified SES sending identity.

ℹ️

Nothing is provisioned "just in case." If you don't need a database, Bronco doesn't create one — and doesn't charge you for one. Shared resources (database, cache, storage) are decided once at the platform level; each service then opts into the ones that actually exist.

🔓

No vendor lock-in. Bronco writes plain, industry-standard Terraform directly into your own AWS account — it lives in .bronco/<env>/ and it is yours. Inspect it, extend it, commit it, or eject and manage it by hand at any time; Bronco never overwrites your manual changes. Every release runs terraform init and terraform validate against a workspace generated exactly as a build generates it, and a subset all the way through terraform plan — so “it runs without Bronco” is a gate, not a promise. There is no proprietary control plane to escape: your infrastructure is yours.

bronco discover

Reconnect to a stack that already exists in AWS — from a new laptop, a teammate's machine, or a fresh checkout. The .bronco/<env>/ workspace is gitignored and machine-bound, so without it commands like deploy, plan, and logs have nothing to run against. bronco discover rebuilds that workspace from the configuration Bronco saved into your account's state bucket during init, and reconnects Terraform to the existing remote state — nothing is re-created.

bash
bronco discover

How it flows:

bash
$ bronco discover

# ? Which AWS account?
#   ● acme-aws    (123456789012) — active
#   ○ client-prod (987654321098)
# ? Which environment do you want to connect to?
#   ● acme-platform / prod
#   ○ internal-tools / staging

  ✔ Config loaded
  ✔ Workspace at .bronco/prod
  ✔ Connected → acme-platform / prod is ready.
ℹ️

Environments created with Bronco 0.7.0 or later are saved to the state bucket automatically on every apply. An older environment shows up the first time you run bronco apply for it from a machine that still has its workspace — after that, it's discoverable from anywhere.

ℹ️

bronco discover is read-only with respect to your AWS resources — it provisions nothing — so, like destroy and outputs, it doesn't require an active license.

bronco plan

Show what Terraform would change without applying. Reads the current state from S3 and diffs it against the saved configuration.

bash
bronco plan [--env <name>]

bronco apply

Re-apply the saved configuration. Only changes that haven't been applied yet are executed. Safe to re-run after a partial failure — Terraform state keeps track of what already exists.

bash
bronco apply [--env <name>]

bronco destroy

Tear down all resources for an environment. Asks for confirmation before destroying. Safe — it shows you the full list before proceeding.

bash
bronco destroy [--env <name>]
⚠️

This deletes your RDS database, Redis cluster, and all other AWS resources for that environment. Make sure you have a backup before destroying production.

bronco outputs

Print all infrastructure endpoints and ARNs for an environment. Useful for onboarding teammates or debugging.

bash
bronco outputs [--env <name>]

# Example output:
  application_url          http://myapp-alb-1234.us-east-1.elb.amazonaws.com
  database_endpoint        myapp-prod.cluster.us-east-1.rds.amazonaws.com:5432
  redis_endpoint           myapp-prod.cache.amazonaws.com:6379
  app_secrets_arn          arn:aws:secretsmanager:us-east-1:...
  cloudfront_url           https://d1234abcd.cloudfront.net
  cdn_bucket_name          myapp-prod-static-assets
  dashboard_url            https://console.aws.amazon.com/cloudwatch/...

bronco report

Generate an infrastructure report — every resource, endpoint, ARN and estimated cost — as a polished .xlsx or .pdf. It also folds in an estimate-vs-actual reconciliation line (your estimated spend against real Cost Explorer spend), so the report doubles as a cost sanity-check. Perfect for client hand-offs and audits.

bash
bronco report [--env <name>] [--format xlsx|pdf] [--brand <file>]
FlagDefaultDescription
--formatxlsxOutput format: xlsx or pdf
--brand(Bronco)Path to a bronco.brand.json to white-label the report — name, accent colour, contact and footer. See Agency.
💡

Agencies: point --brand at your own bronco.brand.json and every report ships with your logo and colours — ready to forward straight to the client. The same branding applies to bronco billing and bronco audit --compliance reports.

bronco.app.yaml

bronco.app.yaml is the deployment contract for your application. It lives in your project root and tells Bronco which services to build, where their Dockerfiles are, what secrets they need, and how to handle the deploy pipeline.
ℹ️

You don't write this file by hand. bronco forge (or bronco deploy, if you skipped that conversation) asks about each service — name, type, schedule, port, which platform resources it needs, Dockerfile path, secrets — and writes the complete file for you. The reference below documents what comes out, in case you want to read it, tweak a value, or add a service later without rerunning the conversation.

Minimal example

Two blocks: resources is the shared infrastructure this environment gets, and services are the apps that connect to it by name.

yaml
resources:
  databases:
    - name: main

services:
  - name: api
    type:  api
    build: { context: ., dockerfile: Dockerfile }
    port:  3000
    needs: [main]
⚠️

The order of your services decides your URLs. The first public service in the list is the load balancer's catch-all and answers on /; every other one answers at /<name> and /<name>/*. Put your frontend first and your API second, and the browser reaches both on one origin with no CORS. bronco forge prints the path beside each service before it builds anything.

⚠️

A service under a prefix needs its health check under the same prefix. The load balancer probes your container directly and keeps the path, so a service reached at /api/* whose routes are mounted under /api must set health: /api/health. With the default /health the target never turns healthy and the rollout fails. Bronco warns about this before it applies.

Full schema

yaml
# The environment's shared infrastructure
resources:
  databases: [{ name: main }, { name: analytics, criticality: bajo }]
  caches:    [{ name: sessions }]       # every list is optional
  buckets:   [{ name: uploads }]
  queues:    [{ name: events }]
  topics:    [{ name: notifications }]
  auth:      true                      # Cognito           (singleton)
  email:     true                      # SES               (singleton)
  domain:    acme.com                  # Route53+ACM+HTTPS (singleton)
  alerts:    ops@acme.com              # who receives the alarms

services:                              # required
  - name:        api
    type:        api                   # web | api | auth | worker | cron | static | migration
    criticality: normal                # bajo | normal | critico | nunca (ignored in dev)
    build:
      context:    ./services/api
      dockerfile: Dockerfile          # relative to context
    port:        3000                  # Bronco injects PORT and health-checks it
    health:      /api/health           # must match where the routes actually live
    needs:       [main, sessions]      # names from the resources block above
    secrets:
      from: ./services/api/.env.production  # JUST the path — Bronco reads the KEY NAMES from it
    env:                                # non-secret config, inline
      LOG_LEVEL: info

  - name:     web                       # static: npm build to S3 + CloudFront, no Docker
    type:     static
    build:    { context: ./web, command: ["npm", "run", "build"] }
    dist:     ./web/dist              # required for static

  - name:     migrate
    type:     migration
    uses:     api                     # required — runs that service's image
    command:  ["npm", "run", "migrate"] # required — runs before the main deploy
    needs:    [main]
ℹ️

Split it by concern if you like. Any bronco.app.<name>.yaml in the project root is merged in automatically — service names must be unique across every file.

ℹ️

Older contracts still work. A file with a top-level secrets_source: block and secrets: as a list of key names is the previous generation of this contract, and Bronco still reads it. New environments use the shape above, where secrets is a path to a .env and Bronco lifts the key names from it — so keys are never typed twice.

Building without a terminal

bronco forge --config <file> runs the whole build with no prompts — for CI, for scripts, and for agents. The file answers what bronco.app.yaml cannot carry: which environment, where it runs, and what ten minutes of downtime costs.

Start from the generated example. It carries every key — required ones live, optional ones commented — and the file it prints builds as-is:

bash
bronco forge --example > bronco.forge.yaml
bronco forge --config bronco.forge.yaml
yaml
# bronco.forge.yaml — sits beside bronco.app.yaml
project:     my-app          # lowercase letters, digits and dashes, 3–32 chars
environment: prod            # dev | staging | prod, or a custom name
region:      us-east-1       # where this environment runs

# How many zones this environment pays to survive — the largest line on the
# bill. Required outside dev; dev always runs minimal and must NOT set it.
#   single : one zone — a zone failure is an outage until AWS restores it
#   spread : two zones for the app, one for the database
#   zonal  : two zones for everything, standby database
resilience: single

# Both are REQUIRED, and null is a real answer — not the same as leaving the
# key out. Absent means "nobody said"; null means "no, none".
domain: null                 # or: acme.com  (then hostedZoneId is required)
alerts: null                 # or: ops@acme.com — who receives the alarms

# The build makes a person type a sentence for these; a file must name them,
# and an absent list is a refusal rather than a yes.
acknowledge: [plaintext-origin]

# Optional
# environmentBase: prod        # REQUIRED when 'environment' is a custom name
# hostedZoneId: Z1D633PJN98FT9 # REQUIRED when 'domain' is set
# emailFrom: no-reply@acme.com # the verified sender, when the contract wires email
# waf: true                    # derived from the environment when absent
# albAccessLogs: true          # derived from the environment when absent
# budget: 200                  # monthly ceiling in dollars
# connections: { api: { main: DATABASE_URL } }
KeyRequiredNotes
projectrequiredLowercase letters, digits and dashes, 3–32 chars
environmentrequireddev | staging | prod, or a custom name
regionrequiredAn AWS region Bronco builds in
resiliencerequiredsingle | spread | zonal. Outside dev only — dev must not set it
domainrequiredUse null to serve on the generated CloudFront URL
alertsrequiredUse null for no alert address
acknowledgerequiredWhen it applies — the build names what is missing and stops
environmentBaseoptionalRequired when environment is a custom name
hostedZoneIdoptionalRequired when domain is set
emailFromoptionalVerified sender, when the contract wires email
waf / albAccessLogsoptionalDerived from the environment when absent
budgetoptionalMonthly ceiling in dollars
connectionsoptionalThe env var each service reads per resource; derived when absent
ℹ️

It refuses rather than guesses. Every value the file does not supply is a hard stop naming the missing key — a headless build never invents an answer to a question about your money.

Service Types

web — Public HTTP service

Runs in ECS Fargate behind the Application Load Balancer. Gets a path-based routing rule (/ for the primary service, /name for others), health check, and autoscaling policy.

FieldRequiredNotes
dockerfilerequiredPath to Dockerfile relative to project root
portrequiredContainer port the app listens on
contextoptionalDocker build context (default: .)
commandoptionalOverride CMD from Dockerfile
secretsoptionalSecret keys to inject from Secrets Manager

api / auth — Public HTTP services

Same shape as web — an ECS Fargate service behind the shared ALB with path-based routing, health checks, and autoscaling. Use them to keep service roles explicit in multi-service stacks.

worker — Queue consumer / background service

Runs in ECS Fargate with no public endpoint. Scales independently. Use for Sidekiq, Celery, BullMQ workers, or any long-running background process.

Worker services automatically get an SQS queue + dead-letter queue (DLQ) provisioned and the SQS_QUEUE_URL environment variable injected. The task role is granted sqs:SendMessage, sqs:ReceiveMessage, sqs:DeleteMessage, and related permissions.

yaml
- name:       email-worker
  type:       worker
  dockerfile: ./Dockerfile
  command:    "bundle exec sidekiq -C config/sidekiq.yml"
  secrets:    [DATABASE_URL, REDIS_URL, SENDGRID_API_KEY]
  # SQS_QUEUE_URL is injected automatically for worker type

You can also add a queue to any non-worker service by setting needs_queue: true:

yaml
- name:       api
  type:       web
  needs_queue: true   # enqueue jobs from the API layer

migration — ECS one-off task

Runs as an ECS one-off task (not a long-running service) before the rolling deploy, and blocks it until it exits with code 0. If it exits non-zero, the deploy stops immediately — no services are updated, so new code never reaches an un-migrated database. Use for database schema migrations, seeds, or any one-shot "before everything else" step.

A migration has no Dockerfile or image of its own. It uses another service's image — the exact one about to be deployed — and overrides the container command. That's the standard pattern for rails db:migrate, prisma migrate deploy, alembic upgrade head: the same image as your app, just a different command. No redundant build, no separate ECR repo.

FieldRequiredNotes
usesrequiredName of another service (a web/api/auth/worker/cron — anything with an image) whose image this migration runs
commandrequiredThe command to run, as an array of strings — e.g. ["npm","run","migrate"]
yaml
- name:    db-migrate
  type:    migration
  uses:    api                       # reuse the api image about to ship
  command: ["npx","prisma","migrate","deploy"]
💡

If the service named in uses never built (e.g. its image failed), the migration is skipped, not failed — running against stale code would be worse. Only a migration that actually runs and exits non-zero stops the deploy.

static — Frontend / S3 + CloudFront

Builds your frontend locally and syncs the output folder to S3, then invalidates CloudFront. Uses MD5 delta detection — only uploads files that changed.

FieldRequiredNotes
build_cmdoptionalLocal command to build the frontend (e.g., npm run build). A string runs through a shell (so &&, pipes, and env expansion work); pass an array (e.g. ["npm","run","build"]) to run the program directly with no shell.
distrequiredPath to the output folder to upload (e.g., ./dist, ./build)
contextoptionalWorking directory for build_cmd (default: project root)
⚠️

The CDN add-on must be enabled in bronco.yaml for static deploys to work. Bronco reads cdn_bucket_name and cloudfront_distribution_id from Terraform outputs.

ℹ️

build_cmd and shells. A string build_cmd runs through your shell, so bronco.app.yaml is executable config — only run bronco deploy in a repo you trust, the same trust you give its Makefile or npm scripts. If the command is assembled from untrusted input, use the array form (["npm","run","build"]): it executes the program directly with no shell, so metacharacters are treated literally.

cron — Scheduled task

Wires an ECS task to an EventBridge Scheduler rule. Bronco provisions the rule, the IAM role to invoke ECS, and the task definition. Use any standard cron expression.

yaml
- name:       daily-report
  type:       cron
  dockerfile: ./Dockerfile
  command:    "node scripts/generate-report.js"
  schedule:   "cron(0 8 * * ? *)"   # 8:00 AM UTC every day
  secrets:    [DATABASE_URL, SENDGRID_API_KEY]

Secrets

secrets is just the path to a .env. Bronco reads the key names from that file at build time and uploads the values at deploy time, so a key is never typed twice and a value never lives in your yaml.
yaml
services:
  - name: api
    secrets:
      from: ./services/api/.env.production   # or the shorthand: secrets: ./api/.env.production

from is environment-aware — it defaults to .env.<env>, resolved relative to the service's context, which is what a monorepo with one .env per service wants. A top-level secrets: { from: … } sets a default every service inherits.

Convention: the .env holds secrets and all of it goes to Secrets Manager; non-secret configuration goes in env: inline in the yaml.

ℹ️

Add your .env files to .gitignore. Never commit real secrets to version control.

Legacy: secrets_source

The two strategies below are the previous generation of this contract, listing key names by hand. Bronco still reads them; new projects use the from path above.

strategy: dotenv

Bronco reads the keys declared under each service's secrets field from your .env.production file and uploads them to the Secrets Manager ARN provisioned by Bronco. Only the declared keys are uploaded — Bronco never reads or uploads keys you didn't list.

yaml
secrets_source:
  strategy: dotenv
  file:     .env.production
ℹ️

Add .env.production to .gitignore. Never commit real secrets to version control.

strategy: already-in-aws

Your secrets are already in Secrets Manager (uploaded manually or by a previous tool). Bronco skips the upload step entirely and proceeds directly to Docker build.

yaml
secrets_source:
  strategy: already-in-aws

bronco deploy — The Application

Run this on every code commit or CI/CD pipeline. Driven by your bronco.app.yaml, bronco deploy packs, ships and updates your application containers — build, push, migrate, roll out — without touching the core infrastructure that bronco forge provisioned. Forge builds the foundation once; deploy ships your code onto it, again and again.

bash
bronco deploy [--env <name>]

Pipeline steps — in order

  1. Secrets upload Reads the .env file and uploads declared keys to AWS Secrets Manager. Skipped if strategy: already-in-aws.
  2. Docker authentication Runs docker login against your ECR registry using temporary credentials from AWS STS. No long-lived credentials stored.
  3. Build + push images Builds a Docker image for every image-backed service and pushes it to the service's ECR repository. Any service missing a Dockerfile gets a starter scaffolded for its detected stack (Node, Python, Go, …) first. No Docker on the machine? Bronco offers to build each image in AWS CodeBuild inside your own account instead — nothing to install locally. A service that genuinely can't be built is skipped, not fatal: the rest of the stack still ships, and the skipped ones are listed at the end.
  4. Run migrations Runs each migration service as an ECS one-off task before the rolling deploy — reusing the image of the service named in uses, pinned to the exact build about to ship, with the container command overridden. Waits for exit code 0. If a migration's upstream image never built, it's skipped; if a migration actually runs and exits non-zero, the deploy stops here — ECS services are not updated.
  5. ECS rolling deploy Calls update-service on each web, api, auth, and worker service in parallel. Waits for servicesStable. The replacement task starts before the old one is stopped, in every environment including dev: a public service keeps serving from the old task until the new one passes the load balancer’s health check, measured from outside the stack at zero seconds of downtime. A worker has no load balancer, so its gate is the weaker one — the new container is running. A deploy that Amazon reports as failing its health check is abandoned on the first such verdict for a single-container service, in about two minutes; a service running two or more containers waits for a second failure, because one container of three losing a race is a deploy that is still working.
  6. Static deploy For each static service: runs build_cmd locally, then syncs the dist folder to S3 with MD5 delta detection. After sync, invalidates CloudFront (/*) so the CDN serves the new version immediately.
  7. Health check Asks two questions of every public service this deploy shipped, and prints both answers: the balancer’s health path — the health field, not a hardcoded /health — and the route the service is actually reachable on. A deploy can finish green while the app is down, because the balancer only ever asks the one path you told it to.
    web — /ready answered 200, / answered 503
    It reports, it never blocks: the answers never move the deploy’s status or its exit code, and a root path answering 404 is an ordinary API, not a disagreement.
💡

If a step fails, fix the issue and re-run bronco deploy. Steps that already completed (e.g., Docker push) are fast or skipped — only the failed step and onward re-run.

bronco rollback

Rolls back an ECS service to the previous task definition revision. Use this immediately after a bad deploy introduces a regression — the previous revision is already warm in ECR, so the rollback completes in seconds without a Docker build.

bash
bronco rollback [--env <name>] [--service <name>]

If you don't pass --service, Bronco lists your services interactively. A confirmation prompt is shown before any change is made.

💡

bronco rollback only affects persistent ECS services (web, api, auth, worker). To roll back a cron task, re-run bronco deploy with the previous code.

bronco logs

Tails the CloudWatch log group that Bronco creates for each ECS service (/ecs/<project>-<env>-<service>). No need to open the AWS Console.

bash
bronco logs [service] [--env <name>] [--lines N] [--follow]

Options:

bash
bronco logs api                   # last 100 lines
bronco logs api --lines 50        # last 50 lines
bronco logs api --follow          # stream new events
bronco logs api --env prod --follow

Custom Domains & SSL

When you run bronco forge and answer yes to "Do you have a custom domain?", Bronco provisions the full SSL stack automatically:

💡

Certificate validation via DNS can take up to 3 minutes during the first bronco apply. Terraform waits automatically — no manual action needed.

Requirements:

bash
$ bronco forge
...
? Do you have a custom domain for this project? Yes
? Apex domain (e.g. acme.com): myapp.com
? Route53 Hosted Zone ID: Z1ABCDEF1234567

After apply, the bronco outputs command shows both the ALB URL and the HTTPS URL.

User Authentication & Email (Cognito + SES)

Two platform capabilities you opt into during bronco forge — each creates one shared resource for the whole stack, the same way the database and cache are shared. A user pool models "who can sign into this product"; splitting it per service would give your API and your frontend separate user bases, which is almost never what you want. Every service that opts in connects to the same pool / sending identity.

Authentication — Cognito

Answering yes to "Do your apps need user authentication?" provisions one Cognito user pool with a public app client (no client secret — safe for SPAs and mobile), a 12-character password policy, email-based sign-up/sign-in, and MFA required in prod (optional in dev/staging). Services that opt in via needs_cognito: true get:

Transactional email — SES

Answering yes to "Do your apps need to send transactional email?" provisions one verified SES sending identity plus a configuration set, so every service's sends land in the same deliverability/bounce metrics. Bronco then asks which address to send from and verifies it the easiest way it can:

Services that opt in via needs_ses: true get:

ℹ️

Cognito and SES are billed per use — active users and emails sent — not a flat monthly fee, and most projects stay within the AWS free tier (50k MAU for Cognito; 3,000 emails/day from EC2/Fargate for SES). Because the bill is driven by usage rather than the resource existing, the live cost pulse during bronco forge won't include them — see Cost Estimates.

yaml
- name:          api
  type:          web
  dockerfile:    ./Dockerfile
  needs_cognito: true   # COGNITO_USER_POOL_ID, COGNITO_CLIENT_ID
  needs_ses:     true   # SES_FROM_ADDRESS, SES_CONFIGURATION_SET

bronco run

Run one command inside your app, in the environment. Your database sits on a private network with no route from your laptop, so a command that needs it has to run where your app runs. bronco run starts a one-off task on that service’s own definition — same image, same environment variables, same secrets, same role, same network — streams the output, and exits with your container’s own exit code.

bronco run "npx prisma migrate status"
bronco run "npx prisma migrate resolve --rolled-back 20260903_add_index" --service api
bronco run "python manage.py showmigrations" --env staging

Quote it. Your command’s own flags belong inside the quotes and Bronco’s belong outside; an unquoted command is refused rather than run half-way. --service picks whose container when there is more than one, and Bronco asks when you don’t. --no-shell execs the words directly, for an image that has no shell at all.

It works in production, where it prints the command and the environment and asks before it runs; --yes is the path for a script. It is not a shell into a running container — the task is separate, so what is serving your traffic is never touched, and nothing has to be installed alongside Bronco.

ℹ️

A blocked migration names this command. When a migration fails and leaves your schema blocked (Prisma’s P3009, Flyway’s failed history, a stuck Knex lock), the deploy prints the exact bronco run line that clears it.

bronco db

SQL against this environment’s own database. Same idea as bronco run, with a Postgres client instead of your image: a one-off task on the environment’s own network, borrowing a deployed service’s connection. The password is fetched by AWS into the container and never reaches your machine.

bronco db "select * from _prisma_migrations order by started_at desc limit 5"
bronco db --file ./repair.sql --env staging
bronco db "select count(*) from users" --database analytics

--database picks which one when the environment has several. There is no interactive prompt: statements in, results out. What the statement returns is written to that service’s log group, at its retention — the screen says so.

bronco watch

Continuous monitoring — in your own account. bronco watch provisions a scheduled, read-only engine (an EventBridge schedule + a Lambda) that runs the cost/audit/risk scans for you on a cadence and pushes a digest to Slack, Teams, Discord, or any generic webhook. No Bronco-hosted backend, no cross-account role — it lives entirely in your AWS account, the same way every other Bronco resource does.

bronco watch

A short conversation sets the digest cadence (weekly, daily, or monthly) and your channel webhooks. Every channel accepts several URLs (comma-separated) so one stack can fan out to multiple workspaces, and the generic webhook receives the raw digest JSON for anything that isn't Slack/Teams/Discord. The engine then watches four signals and pushes a proactive alert the moment one fires:

Every digest also reconciles your estimated spend against actual Cost Explorer spend over the same window, so the bill never surprises you — and so a non-technical stakeholder watching the Slack channel sees the ROI without ever opening a terminal.

SubcommandWhat it does
bronco watchSet up + provision the engine (a short conversation)
bronco watch statusShow the cadence, signals, and channels in effect
bronco watch digestRun a digest right now and send it
bronco watch testSend a sample digest to verify the channels are wired
bronco watch offTear the engine down

Setup requires a license (it provisions infrastructure, like init/apply); status, digest, and off never do — a lapsed license must never trap you with a running engine you can't see or stop.

Optional: autonomous remediation on a schedule. During setup you can let the engine apply the same policy-approved fixes bronco fix --auto would — so a security regression is closed within hours, not whenever someone next opens a terminal. It's off by default and strictly gated: it needs a bronco.policy.json with auto rules, and the Lambda's IAM role gains write permission only for the two narrow, reversible fixes (revoke an over-broad security-group rule, enable S3 Block Public Access) — never delete, never IAM, never on by default. Every action is recorded and reversible with bronco fix --undo, and each digest reports what it fixed.

ℹ️

Read-only unless you opt in. Without the opt-in, the watch engine's role is strictly read-only — it reports, it never changes your infrastructure.

bronco drift

Declared vs live — what changed outside Bronco. bronco drift runs a read-only Terraform refresh and reports the resources someone changed in the AWS console or another tool, so your last applied state no longer matches reality.

bronco drift

It's baseline-diffed, which is what makes it usable. A freshly-applied stack is never truly drift-free under a raw refresh — the AWS provider always reports bookkeeping differences on parent resources (a security group's computed rules, a route table's routes, an EIP's NAT association). So the first run records that steady-state as a baseline, and every run after flags only what's genuinely new — a real out-of-band change, not provider noise. Run --reset to re-establish the baseline after an intentional change, and --full (license) for the full itemized diff.

bronco drift exits non-zero when new drift is found, so a CI pipeline can gate on a clean stack. It needs a Bronco-built workspace — "declared vs live" only means something for a stack Bronco provisioned.

bronco fix

This is where Bronco stops reporting and starts working. Instead of a list a human has to act on, bronco fix turns audit findings into an ordered, reversible remediation plan and applies it within an explicit policy you set. It always shows the plan first, never deletes anything autonomously, records every action with an undo, and writes its audit log to your own state bucket — never a Bronco backend.

The first command that writes to AWS without a human stepping through every change. That's why its safety model is the policy file: each finding class is auto (Bronco may apply it unattended), approve (apply only after a y/N), or never (alert only). With no bronco.policy.json present, every fix needs your approval — exactly the behavior of bronco audit --fix today.

bash
bronco fix                 # scan → plan → apply (auto runs, approve asks)
bronco fix --dry-run       # print the plan and stop — change nothing (free)
bronco fix --auto          # non-interactive: apply ONLY policy-authorized auto fixes (CI/cron)
bronco fix --undo <id>      # revert a previously applied action
FlagDescription
--dry-runRender the ordered plan and exit without writing anything. A free preview — no license required.
--autoApply only the actions your policy marks auto, with no prompts. Everything else is reported as "needs approval". For CI / cron.
--undo <id>Reverse a recorded action by its id (shown after each fix and in bronco timeline), restoring the captured prior state.
💡

The plan is a free previewbronco fix --dry-run shows exactly what would change, in what order, what's reversible, and each action's policy decision, with no license and no writes. Applying a fix is the gated, licensed action.

What it can fix (reversibly)

Cost-waste items that require deleting a resource (unattached volumes, idle IPs, old snapshots) are deliberately not auto-fixable — destructive actions are never autonomous. The fix set grows release over release.

⚠️

Destructive changes are never automatic. Deleting a resource always requires explicit, per-action human approval — even with a policy that says auto. A non-reversible fix can never be auto either. Bronco never takes an action you didn't pre-authorize.

bronco.policy.json

The policy file lives in your project root (hand-written, like bronco.app.yaml) and is the single place you pre-authorize what bronco fix may do unattended. Match a finding by its checkId, its category, or *; the optional allowlist fences fixes to resources you name.

json — bronco.policy.json

  "version": "1",
  "default": "approve",            // decision when nothing below matches
  "rules": [
     "match": "sec-sg-open",   "action": "auto" ,   // by checkId
     "match": "Storage",       "action": "approve" ,// by category
     "match": "*",             "action": "never"      // catch-all
  ],
  "allowlist":  "resources": ["sg-0a1b2c3d"] 
ActionMeaning
autoApply unattended (only ever granted to reversible, non-destructive fixes).
approveApply only after an explicit y/N. The default when there's no policy at all.
neverReport only — Bronco will never write this class.
ℹ️

The deeper fence is IAM: roles Bronco creates carry a permissions boundary that caps what they can ever do. The policy allowlist is the belt-and-suspenders layer you control per finding class — a fix on a resource outside it is refused before any API call.

bronco simulate

Predictive cost — the spend delta of a change, before you apply it. Resize the stack, add a service, enable a database or the WAF, promote an environment to prod — bronco simulate forecasts the exact monthly difference, using the same estimator behind the init cost pulse, grounded against your real Cost Explorer spend. Read-only — it writes nothing to AWS.

bash
bronco simulate --size large       # price a resize
bronco simulate --add worker       # price adding a service
bronco simulate --waf              # price enabling the WAF
bronco simulate                    # interactive: pick a change
bronco cost --simulate             # same engine, under the cost command
~ bronco simulate --size large
  Cost simulation
  Change             size → large
  Current est.       $90.00/mo
  Projected est.     $188.00/mo
  Delta              +$98.00/mo
  Estimate vs actual $84.10 actual vs $90.00 est. (-6.6%)
  Projected actual   $182.10/mo

  What this touches
    · size small → large

Every bronco plan now prints the estimated monthly cost too, and — once the intelligence graph has a prior apply — the delta versus what you last shipped. The cost pulse became predictive, not just descriptive.

bronco timeline

The infrastructure intelligence graph. Bronco records every meaningful change — init, apply, deploy, destroy, drift, fix, undo, simulate — as an append-only event in your own state bucket, with the cost delta and the reason behind it. bronco timeline queries that history so "what changed last month, and why?" is one command instead of nobody's notes.

bash
bronco timeline                    # recent events for the environment
bronco timeline --kind fix         # only remediation actions
bronco timeline --since 30d        # last 30 days (30d | 12h | ISO date)
bronco timeline --full             # the complete history
FlagDescription
--kindFilter by event kind: init · apply · deploy · destroy · drift · fix · undo · simulate
--sinceOnly events since a window (30d, 12h) or an ISO date
--fullShow the entire history rather than the most recent entries
ℹ️

The graph also maps app-layer changes to infrastructure: adding a worker service is recorded as "added the SQS queue + DLQ + the IAM grants", so the timeline explains how an application change reshaped your cloud topology.

bronco billing

The agency layer turns Bronco's scans, deploys and reports into a multi-client operations cockpit. Everything here is read-only and account-agnostic — only Cost Explorer is billable — and every report can be white-labeled with your own brand. Built for agencies and teams running many AWS accounts.

Cost allocation per project, environment or client. bronco billing reads Cost Explorer grouped by a cost-allocation tag and breaks your spend down so every client invoice maps to real infrastructure cost — not a guess.

bash
bronco billing --by project|environment|tenant [--format xlsx|pdf] [--brand <file>]
FlagDefaultDescription
--byprojectGroup spend by project, environment, or client tenant
--formatxlsxRender the breakdown to xlsx or pdf
--brand(Bronco)White-label the report with your bronco.brand.json

--by tenant aggregates across every connected account, summing each account's spend under the client tenant you assigned with bronco tenants — one number per client, across all their accounts.

ℹ️

Cost Explorer must be enabled, and the Project / Environment cost-allocation tags activated in the Billing console — AWS only surfaces tag-grouped spend once a tag is activated. Every resource bronco forge creates is already tagged; activating the tags is a one-time click in your account.

White-label branding — bronco.brand.json

Drop a bronco.brand.json in your project (or pass it via --brand) and every generated report — billing, audit --compliance and report — carries your agency's identity instead of Bronco's.

json — bronco.brand.json

  "name":    "Acme Cloud",
  "accent":  "#2563EB",
  "contact": "ops@acme.example",
  "footer":  "© Acme Cloud — confidential"

bronco audit --compliance

Map your live security posture to a framework. The same scan behind bronco audit, crosswalked to SOC2 or HIPAA controls — each control marked pass / fail / not-assessed from real findings, with a posture summary you can hand to a client or an auditor.

bash
bronco audit --compliance soc2|hipaa [--format xlsx|pdf] [--brand <file>]

The preview summary (totals per status) is free; the full per-control posture and the branded report unlock with a subscription.

⚠️

This is a posture mapping from automated checks, not a certification. It shows you — and your client — where you stand against the framework's technical controls and what to fix first. It is not a substitute for a formal audit.

bronco tenants

Group connected AWS accounts (see bronco login) by client tenant. The tenant label flows into bronco billing --by tenant, so spend rolls up per client across all of their accounts.

bash
bronco tenants                       # list accounts and their tenant
bronco tenants set acme-prod acme    # assign account 'acme-prod' to tenant 'acme'

Tenant metadata lives in ~/.bronco/accounts.json alongside the account index — no secrets, just labels.

bronco roles

Scope teammates to a role per accountadmin, developer or viewer — so destructive commands are gated for the people who shouldn't run them.

bash
bronco roles                          # show the active account's role
bronco roles set acme-prod viewer     # set the role for an account (CLI guardrail)
bronco roles policy viewer            # print the IAM policy that ENFORCES a role
RoleCan do
adminEverything, including destroy and account management
developerdeploy · rollback · logs + all read-only scans; no destroy
viewerRead-only: cost · audit · risk · outputs · report

Enforce a role in IAM. The CLI gate is a guardrail; for real enforcement, bronco roles policy <role> emits a least-privilege IAM policy document you attach to a teammate's IAM identity, so AWS itself enforces the tier. viewer is provably read-only (every action is a Describe/List/Get); developer adds service-scoped management plus a hard Deny on identity governance (no IAM users/keys, no Organizations); admin is unrestricted. The JSON prints to stdout so it pipes cleanly:

bash
bronco roles policy viewer > BroncoViewer.json
aws iam create-policy --policy-name BroncoViewer --policy-document file://BroncoViewer.json
aws iam attach-user-policy --user-name teammate --policy-arn <created-arn>
ℹ️

Two layers, your choice. The CLI gate (roles set) catches accidents without touching AWS; roles policy is the hard, AWS-enforced version. Bronco never attaches the policy for you — that IAM write stays in your hands. Accounts with no role set default to admin, so existing setups stay unrestricted.

bronco ask

A natural-language, read-only front door to the whole command surface. Ask a question in plain English; Bronco reads your real account data (cost, audit, risk, timeline) and answers — no flags to remember, no docs to grep.

bash
bronco ask "what's my biggest cost driver this month?"
bronco ask "which security findings are critical, and why?"

Inference runs on your own model access — set ANTHROPIC_API_KEY (your own Anthropic key) or BRONCO_AI_PROVIDER=bedrock to use Claude on Bedrock in your own AWS account, signed with your existing credentials. There is no Bronco inference server: your prompts and your infrastructure data never leave your machine and your cloud. ask is read-only and needs no license.

bronco chat

A multi-turn session that goes one step further than ask: it can propose a change — a fix, an apply, a deploy — and carry it out. Crucially, every write still flows through the existing gates: the policy + approval that protect bronco fix and the confirmation that protects apply. The AI proposes; it never gains a new write authority of its own.

bash
bronco chat
# you › close my worst security risk
# bronco › Port 22 is open to 0.0.0.0/0 on sg-0a1b. Revoke it? [y/N]
ℹ️

Same model-access model as ask (your key or in-account Bedrock). A proposed write is recorded to your change timeline with its conversational provenance, and is reversible with bronco fix --undo.

bronco mcp

Serve Bronco as a Model Context Protocol server so an AI coding agent — Cursor, Claude Code, anything MCP-capable — can call it directly. The AI built the app fast; bronco mcp lets it reach for a deterministic, safe deploy/ops primitive instead of free-handing Terraform with your credentials.

Every tool exposed over MCP is read-only — the same diagnose surface as bronco ask (diagnose, cost_report, audit_security, risk_check, timeline, simulate). The agent can read your account and propose the exact command; it can never deploy, fix or change anything itself. Writes always stop at you, at your terminal — the AI gets leverage, never your write authority.

your agent's MCP config
{
  "mcpServers": {
    "bronco": { "command": "bronco", "args": ["mcp"] }
  }
}

It speaks JSON-RPC over stdio with zero extra dependencies, resolves your AWS context lazily on the first call (so it starts instantly), and keeps stdout pure protocol — Bronco's own output goes to stderr. Runs on your existing AWS credentials, in your account, no Bronco backend.

bronco ci generate

Reads your live Terraform outputs and generates a complete, pre-filled CI/CD pipeline YAML for your provider. All values — ECR URLs, cluster name, bucket, CloudFront distribution ID — are pulled from your actual infrastructure.

bash
bronco ci generate [--provider github|azure|gitlab|bitbucket] [--env <name>] [--branch <name>]
FlagDefaultDescription
--providergithubPipeline provider: github, azure, gitlab, bitbucket
--env(prompt)Target environment to read outputs from
--branchmain/staging/developBranch that triggers the pipeline

GitHub Actions — OIDC (recommended)

When --provider github is used, Bronco does two things:

  1. Provisions an IAM OIDC provider for token.actions.githubusercontent.com in your AWS account
  2. Creates an IAM role with a trust policy scoped to your specific repository and branch

The generated pipeline uses role-to-assumeno AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY secrets are needed. GitHub exchanges a short-lived OIDC token for temporary AWS credentials automatically.

bash
bronco ci generate --provider github --env prod

  ✓ OIDC provider: token.actions.githubusercontent.com
  ✓ IAM role: bronco-acme-platform-github-deploy
  ✓ Written: .github/workflows/deploy.yml

The generated .github/workflows/deploy.yml looks like this:

yaml — .github/workflows/deploy.yml (generated)
name: Deploy to prod
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/bronco-acme-platform-github-deploy
          aws-region:     us-east-1

      - name: Reconnect · acme · prod
        run: npx bronco-cli@1.2.0 discover --project acme --env prod

      - name: Deploy · acme · prod
        run: npx bronco-cli@1.2.0 deploy --project acme --env prod

Azure Pipelines

Output: azure-pipelines.yml. Uses static AWS credentials stored as Azure pipeline variables.

⚠️

In your Azure DevOps project, add AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as secret pipeline variables. Use an IAM user with the least-privilege policy Bronco describes in the generated file comments.

GitLab CI

Output: .gitlab-ci.yml. Uses GitLab CI/CD variables for AWS credentials.

In your GitLab project: Settings → CI/CD → Variables. Add AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as masked, protected variables.

Bitbucket Pipelines

Output: bitbucket-pipelines.yml. Uses Bitbucket Repository Variables for AWS credentials.

In your Bitbucket repo: Repository settings → Pipelines → Repository variables.

Example: SaaS API + React Frontend

A Node.js API, a React frontend built with Vite, and a database migration task.

yaml — bronco.app.yaml
services:
  - name:       api
    type:       web
    dockerfile: ./Dockerfile
    port:       3000
    secrets:    [DATABASE_URL, REDIS_URL, JWT_SECRET, STRIPE_SECRET_KEY]

  - name:       db-migrate
    type:       migration
    uses:       api
    command:    ["npx","prisma","migrate","deploy"]

  - name:       frontend
    type:       static
    build_cmd:  "npm run build"
    dist:       ./frontend/dist
    context:    ./frontend

secrets_source:
  strategy: dotenv
  file:     .env.production

Example: Rails + Sidekiq

Same Dockerfile, two processes — one for Puma (web) and one for Sidekiq (worker).

yaml — bronco.app.yaml
services:
  - name:       web
    type:       web
    dockerfile: ./Dockerfile
    port:       3000
    command:    "bundle exec puma -C config/puma.rb"
    secrets:    [DATABASE_URL, REDIS_URL, SECRET_KEY_BASE, S3_BUCKET, MAILGUN_API_KEY]

  - name:       sidekiq
    type:       worker
    dockerfile: ./Dockerfile
    command:    "bundle exec sidekiq -C config/sidekiq.yml"
    secrets:    [DATABASE_URL, REDIS_URL, MAILGUN_API_KEY]

  - name:       db-migrate
    type:       migration
    uses:       web
    command:    ["bundle","exec","rails","db:migrate"]

secrets_source:
  strategy: dotenv
  file:     .env.production

Example: FastAPI + Celery + Beat

yaml — bronco.app.yaml
services:
  - name:       api
    type:       web
    dockerfile: ./Dockerfile
    port:       8000
    command:    "uvicorn app.main:app --host 0.0.0.0 --port 8000"
    secrets:    [DATABASE_URL, REDIS_URL, OPENAI_API_KEY]

  - name:       celery-worker
    type:       worker
    dockerfile: ./Dockerfile
    command:    "celery -A app.worker worker --loglevel=info"
    secrets:    [DATABASE_URL, REDIS_URL, OPENAI_API_KEY]

  - name:       celery-beat
    type:       worker
    dockerfile: ./Dockerfile
    command:    "celery -A app.worker beat --loglevel=info"
    secrets:    [DATABASE_URL, REDIS_URL]

  - name:       db-migrate
    type:       migration
    uses:       api
    command:    ["alembic","upgrade","head"]

  - name:       frontend
    type:       static
    build_cmd:  "npm run build"
    dist:       ./frontend/build
    context:    ./frontend

secrets_source:
  strategy: dotenv
  file:     .env.production

Example: Microservices (3 independent APIs)

Each service has its own Dockerfile, its own set of secrets, and its own migration. notifications-service is a worker — no public URL, and it consumes the SQS queue Bronco provisions for it.

yaml — bronco.app.yaml
services:
  - name:       auth-service
    type:       web
    dockerfile: ./services/auth/Dockerfile
    context:    ./services/auth
    port:       3001
    secrets:    [DATABASE_URL, JWT_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET]

  - name:       billing-service
    type:       web
    dockerfile: ./services/billing/Dockerfile
    context:    ./services/billing
    port:       3002
    secrets:    [DATABASE_URL, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET]

  - name:       notifications-service
    type:       worker
    dockerfile: ./services/notifications/Dockerfile
    context:    ./services/notifications
    secrets:    [DATABASE_URL, SENDGRID_API_KEY, TWILIO_AUTH_TOKEN]

  - name:       db-migrate-auth
    type:       migration
    uses:       auth-service
    command:    ["npm","run","migrate"]

  - name:       db-migrate-billing
    type:       migration
    uses:       billing-service
    command:    ["npm","run","migrate"]

  - name:       dashboard
    type:       static
    build_cmd:  "npm run build"
    dist:       ./dashboard/dist
    context:    ./dashboard

secrets_source:
  strategy: dotenv
  file:     .env.production

Example: Next.js SSR (server-rendered)

Next.js in server mode — not a static export. Runs as a container on ECS.

yaml — bronco.app.yaml
services:
  - name:       nextjs
    type:       web
    dockerfile: ./Dockerfile
    port:       3000
    secrets:    [DATABASE_URL, NEXTAUTH_SECRET, NEXTAUTH_URL, STRIPE_SECRET_KEY]

  - name:       db-migrate
    type:       migration
    uses:       nextjs
    command:    ["npx","prisma","migrate","deploy"]

secrets_source:
  strategy: dotenv
  file:     .env.production
ℹ️

For Next.js static exports (next export), use type: static instead, with build_cmd: "npm run build && npm run export" and dist: ./out.

Sovereignty & security — no backend, ever

The line Bronco never crosses. A platform-as-a-service is the bridge between your repo and your cloud — it watches your repository from its servers, runs your compute in its account at a markup, and holds your state in its database. Bronco installs the bridge and steps away: the pipeline lives in your repo, the stack and all of its state live in your AWS account, and no Bronco-hosted backend ever sits in the path. This is not a feature toggle — it's the architecture, and every command on this page honours it.

🔒

What this means concretely: if Bronco's website went offline tomorrow, your pipelines would keep deploying, your watch engine would keep alerting, your timeline and receipts would keep being written — because none of them depend on us. The only thing that talks to Bronco is license activation (Lemon Squeezy), and a lapsed license never gates destroy, watch off, status, or reading your own data.

DimensionA platform-as-a-serviceBronco
Where compute runsTheir account, at their priceYour AWS, at AWS’s price, unmarked
Where state livesTheir databaseYour encrypted S3 (the sovereign state bucket)
CredentialsStored on their serversNever leave your machine · CI uses OIDC, no static keys
AI inferenceTheir model, your prompts proxiedYour own Anthropic key or in-account Bedrock
If their service is downYour deploys stopNothing of theirs is in the path
Your billSubscription + a markup on hidden computeFlat subscription + your raw AWS bill, unmarked

The sovereign state bucket

Bronco auto-provisions one hardened S3 bucket per AWS account (versioned, encrypted, public-access-blocked) and never asks you to manage it. Everything Bronco remembers between runs lives there, in your account:

One seam reads and writes all of it — a single object-store abstraction over your bucket. There is no Bronco-side mirror of any of it. Delete the bucket and Bronco forgets; you own the only copy.

The trust boundary — what runs where

Three planes, and the boundary between them is the whole product:

PlaneLives inExamples
Your machine / CI runnerWhere you invoke BroncoThe CLI binary, your AWS credentials (resolved, never persisted by Bronco), Docker builds, AI prompts
Your AWS accountEverything Bronco provisions or readsThe stack, the state bucket, the watch Lambda + EventBridge schedule, Bedrock inference (if used)
Bronco (us)License activation onlyLemon Squeezy activation API — a key in, a yes/no out. No infra access, no repo access, no data.
ℹ️

What Bronco never has: a standing role into your account, a GitHub App with repo scopes, a copy of your code, your credentials, your state, your prompts, or anything in the path of your uptime. Connecting a repo writes the pipeline as a file you commit; CI authenticates to AWS via an OIDC role scoped to that one repo. The AI operator can propose writes but never holds a write authority of its own — every change still flows through the same fix / apply / Operate gates a human controls.

This is why the locked product principle holds even with AI in the loop: your infra is yours. See also the compliance evidence packs for how this boundary is evidenced and audited.

Command Reference

CommandDescription
bronco forgeConversational infra provisioning wizard
bronco discoverReconnect to an existing stack from AWS (new machine or teammate)
bronco plan [--env]Show what Terraform would change
bronco apply [--env]Re-apply saved configuration
bronco destroy [--env]Tear down all resources for an environment
bronco outputs [--env]Print all endpoints and ARNs
bronco report [--env] [--format] [--brand]Branded infrastructure report (xlsx / pdf) + cost reconciliation
bronco deploy [--env]Full application deployment pipeline
bronco rollback [--env] [--service]Roll back a service to previous task definition
bronco logs [svc] [--follow]Tail CloudWatch logs for a service
bronco cost / audit / risk [--full] [--all-regions]Agnostic spend, security & reliability scans — on any account
bronco drift [--env] [--full] [--reset]Detect resources changed outside Bronco (declared vs live)
bronco fix [--auto] [--dry-run] [--undo <id>]Autonomous, policy-gated, reversible remediation
bronco simulate [--size] [--add] [--waf]Forecast a change's monthly cost before you apply
bronco timeline [--kind] [--since] [--full]Query the change history (what changed, when, and why)
bronco watch [on|off|status|digest|test]Continuous monitoring → Slack/Teams/Discord/webhook digests & alerts (opt-in auto-fix)
bronco billing --by project|environment|tenantCost allocation per project / environment / client tenant
bronco audit --compliance soc2|hipaa|iso27001|pci [--evidence]Map live findings to a framework; --evidence exports a dated, branded evidence pack
bronco tenants [set <acct> <tenant>]Group connected accounts by client tenant
bronco roles [set <acct> <role>] [policy <role>]Scope teammates per account; policy emits the enforcing IAM policy
bronco ask "<question>"Ask your infrastructure a question in plain English (read-only, your model access)
bronco chatMulti-turn operator — read, propose & approve changes through the existing gates
bronco ci generateGenerate CI/CD pipeline YAML
bronco login [--aws] [--use name]Connect an AWS account, or switch the active one
bronco accounts [--use name]List connected AWS accounts and pick the active one
bronco logout [name]Forget a connected account's local key (AWS untouched)
bronco activate [key]Activate license on this machine
bronco licenseShow license status
bronco deactivateRelease this machine's license slot

YAML Schema Reference

FieldTypeRequiredDescription
servicesarrayYesList of service definitions
services[].namestringYesMust match the service name used in bronco forge
services[].typestringYesweb | api | auth | worker | cron | static | migration
services[].dockerfilestringimage typesPath to Dockerfile, relative to project root. Not used by static (builds locally) or migration (reuses another service's image). Auto-scaffolded if missing — see bronco deploy.
services[].contextstringNoDocker build context. Default: .
services[].portnumberweb / api / authContainer port
services[].commandstring · string[]migrationOverride CMD from Dockerfile. Required for migration (as an array, e.g. ["npm","run","migrate"]); optional string override for other types
services[].secretsstring[]NoSecret keys to inject from Secrets Manager
services[].build_cmdstring · string[]staticLocal command to build the frontend. String runs via a shell; array (e.g. ["npm","run","build"]) runs with no shell.
services[].diststringstaticOutput folder to upload to S3
services[].usesstringmigrationName of the service whose image this migration runs (migrations have no image of their own). All migrations run before the rolling deploy.
services[].schedulestringcronEventBridge cron expression, e.g. rate(1 hour) or cron(0 2 * * ? *)
services[].needs_queueboolNoCreate an SQS queue + DLQ; inject SQS_QUEUE_URL. Auto-set to true for worker type.
services[].needs_cognitoboolNoConnect to the shared Cognito user pool; inject COGNITO_USER_POOL_ID / COGNITO_CLIENT_ID. Only possible when authentication is enabled at the platform level — see User Authentication & Email.
services[].needs_sesboolNoSend through the shared verified SES identity; inject SES_FROM_ADDRESS / SES_CONFIGURATION_SET. Only possible when transactional email is enabled at the platform level.
services[].db_name_overridestringNoIn shared data-pattern mode: use a different logical database name for this service within the shared PostgreSQL instance. In isolated mode: the initial database name created on this service's own dedicated instance (defaults to the project's db_name otherwise)
secrets_sourceobjectYesHow secrets are sourced
secrets_source.strategystringYesdotenv | already-in-aws
secrets_source.filestringdotenvPath to .env file

Cost Estimates

There's no single lump-sum number. The estimate is live and compositional — it starts at the cost of the always-on platform substrate (VPC/NAT, Secrets Manager, CloudWatch, remote state) and grows by exactly one line item every time you confirm something that costs money: a database, a cache, file storage, a service. You watch the running total — the "cost pulse" — build in front of you during bronco forge, instead of being handed one figure at the end and wondering what's in it.

What feeds the total:

Line itemWhen it's addedDriven by
Platform substratealwaysenvironment (NAT gateway count follows HA posture: ~$18/mo for dev/staging, ~$50/mo for prod)
Load balancer (ALB)once any service is publicflat ~$18/mo, shared across all public services
Databaseonly if you opt in at the platform levelsize — small/medium/large ≈ $15 / $30 / $60 per mo
Cacheonly if you opt in at the platform levelsize — small/medium/large ≈ $12 / $24 / $48 per mo
Storage / CDNonly if you opt in at the platform levelflat ~$6/mo baseline (private S3 + CloudFront; scales with usage beyond this)
Per-service computeonce per service you addsize — small/medium/large ≈ $9 / $18 / $38 per mo per service
ℹ️

Shared vs. isolated changes the multiplier, not just the wiring: in shared mode (the default) the database and cache rows above are flat — one instance, one line, no matter how many services use it. In isolated mode, every service that opted into a database (or cache) gets its own dedicated instance and its own generated credentials, so that line becomes (services that need it) × size price instead of a flat fee — two services with their own database at small size means two ≈ $15/mo lines, not one. The live cost pulse reflects this the moment you add each service, so the running total never quietly under-counts what isolation actually costs.

The whole sum is then scaled by an environment multiplier that mirrors its resilience posture — dev ×1, staging ×1.1, prod ×2.2 (multi-AZ database, multi-AZ cache, and a NAT gateway per AZ roughly double the bill). This is the other half of the "two dials": size sets each resource's footprint, environment sets how many of them exist and how durable they are — and the estimate reflects both. A custom environment is priced exactly like the preset it's based on, so a qa built on staging uses the ×1.1 multiplier and the staging substrate line.

Figures are deliberately rounded and conservative (USD, on-demand, us-east-1) — meant to set expectations before apply, not to be an invoice. NAT Gateway data charges and CloudFront/S3 usage beyond the baseline are not included, since these scale with traffic rather than with what you provision.

ℹ️

Not in the estimate, by design: SQS, Cognito, and SES are pay-per-use — their bill is driven by usage (messages processed, active users, emails sent), not by the resource existing. Inflating the estimate with a guessed number would make it less honest, not more — so bronco forge calls this out with a note when you opt into them, instead of folding a made-up figure into the running total.

Troubleshooting

No Docker on this machine

You're not stuck. When Bronco doesn't find a running Docker, bronco deploy offers to build each image in AWS CodeBuild inside your own account instead — it zips each service's build context, uploads it, and builds in the cloud. A few extra minutes per service and a small CodeBuild charge, but nothing to install. Prefer local builds? Install Docker Desktop and re-run bronco deploy — Bronco picks it up automatically.

A service was skipped during deploy

Build/migration failures are non-fatal by design — the rest of the stack still ships, and Bronco lists each skipped service with the reason at the end. Common causes: an unrecognized stack Bronco couldn't scaffold a Dockerfile for, a failed image build, or a migration whose uses image never built. Fix the cause and re-run bronco deploy — only the skipped services need to ship.

Migration exits non-zero

Check the ECS task logs in CloudWatch: Log groups → /ecs/<project>-<env>-<migration-name>. Fix the migration, then re-run bronco deploy.

ECS service never becomes stable

The new task is likely crashing on startup. Check CloudWatch logs for the service. Common causes: wrong port, missing secret key, failed database connection on startup.

Static deploy succeeds but old content is still served

CloudFront invalidation can take 30–60 seconds to propagate. If it's been more than 2 minutes, check the CloudFront console for the invalidation status.

bronco ci generate: "OIDC provider already exists"

A GitHub OIDC provider already exists in your AWS account (only one is allowed per account). This is fine — Bronco re-uses the existing provider and only creates a new IAM role.

terraform init fails on first bronco apply

Usually a credentials issue. Run bronco outputs — if that also fails, re-enter your AWS credentials. Make sure the IAM user or role has the permissions Bronco requires (see the IAM policy in .bronco/<env>/).

Changelog

What changed in each release. Bronco follows semantic versioning, and every release is certified against real AWS — provision → deploy → rollback → destroy across several stack shapes, each proven back to $0, plus deployer security probes — before it is published.

🕓

These entries are a record, not a description of today. Each one says what was true at its own release — including plan names, prices and check counts that have changed since. What Bronco costs now is in the pricing section; what it does now is the rest of this page.

v1.2.0 September 2026 Latest

A brand-new AWS account can build now. Bronco's deploy identity was missing a permission AWS asks for only the first time a service is used in an account, so the first build in a fresh account failed on three resources at once — and an account that had used those services before never showed it. Beyond that, what an upgrade from 1.1.1 changes, in the order it is met: connecting an account no longer needs a browser, the first questions of a build open on what your repository and your machine already say, a build that fails part-way says so while it is happening, and the teardown and the check that verifies it now read one verdict.

Fixed

  • A brand-new AWS account can build now. ELB, ECS, RDS, ElastiCache and ECS autoscaling each need a role AWS creates for itself the first time that service is used in an account — and creating it requires a permission the deploy identity did not have. In an account that had used those services before, the roles already existed and nothing failed, which is why this only ever hit someone starting fresh: three resources failing at once, a few minutes into the first build. The permission is now granted, scoped to exactly the services Bronco's stack needs, and verified end to end against an account that had never used any of them — 100 resources, 10 minutes 31 seconds, a live URL answering in 0.4 seconds.
  • A production build can create its own web firewall. The WAF that production posture turns on needs write permissions the deploy identity was missing.
  • The "create a deploy user for me" CloudFormation template is current again. It is hosted outside the package, and it had fallen two versions behind — so an account set up through the browser received an identity that could not run the free diagnosis. A release gate now compares the hosted file with the shipped one byte for byte, and fails if it cannot read it.
  • A preflight that cannot check anything says so, and asks. Before a build starts, Bronco asks the account about quotas and past refusals. When the credentials in use could not answer any of those questions, it printed seven separate warnings and built anyway. It now states it as one fact, groups the reasons, names the command that fixes the common cause, and asks before creating anything.
  • An account that belongs to an AWS organization no longer gets a false warning. Such an account has no Free Tier plan of its own, which is an answer rather than a failure to read, and it was reported as the latter on every build.
  • A permission failure no longer suggests working around a safety fence. Some actions are refused by Bronco's own permission set on purpose — deleting a database it did not create, reading another application's secret, sharing a database snapshot with another AWS account. Those refusals were being reported as missing permissions, so the screen suggested an upgrade that cannot change them and, in one case, suggested doing it with administrator credentials instead. They are now named as the deliberate controls they are, with what each one protects, and no workaround is offered.
  • The firewall permissions are fenced like every other destructive one. The permission to build a production web firewall also allowed removing one from any resource in the account. It is now limited to the firewalls Bronco created, the same way database and cache deletion already was.
  • bronco deployer upgrade works on the machine the setup leaves behind. Connecting an account scopes your pasted key down to a deploy identity and does not keep the original — so the upgrade, which needs broader permissions, had nothing to use and pointed at a console page that does not exist for identities created this way. It now asks for a key for that one command, uses it in memory, and stores nothing. bronco watch on does the same.
  • bronco deployer upgrade on an account with no deploy identity says so. It used to create permission policies attached to nothing and report success.
  • A preflight that checked nothing now asks, even when some reads answered. The check for "did anything get verified" was counting answers rather than checks, so a run where every limit lookup failed but two unrelated reads succeeded went ahead silently. And a non-interactive apply in that state exited with a success code; it now exits with a failure and says why.
  • Connecting an account asks the key question once. It asked twice, and one of the answers quietly stored the key you pasted after promising not to.
  • bronco deploy --build codebuild is honoured even when Docker is running. It was ignored there, which is exactly the case where it is worth forcing: a local Docker whose images your cloud tasks cannot run. An unrecognised value is now refused instead of behaving like no flag.
  • A permission failure now names the fix. When the denied action is one this release grants and the identity in the account predates it, bronco deployer upgrade is named on the failure screen. When the action is deliberately withheld, the screen says why and how to run that step another way. When no Bronco release grants it, it says that too.
  • The port your app actually listens on. Bronco reads it from your docker-compose.yml or your Dockerfile's EXPOSE and offers THAT — and the screen says which file it came from, so a number read out of your repo never looks like one Bronco made up. When nothing in the repo names a port, the row says so in as many words. Previously the question opened on 8080 whatever your repo said, which is a load balancer health-checking a port nothing listens on.
  • The region, offered from what your machine already knows. Your project's other environments first, then AWS_REGION, then the account you connected, then ~/.aws/config — and the question names the source. A project already spread across two regions is offered neither, because picking one would be a guess. It used to open on us-east-1 for everybody.
  • A build that fails half-way says so while it is happening. The progress line carries a live count of failed resources, and it is the last thing dropped on a narrow terminal. Several resources dying of one cause are reported as one cause, once — so a run stops looking undecided when it is not.
  • A run you interrupt no longer breaks the next one. Stopping Bronco during setup used to leave a half-written component and a wall of text about architectures and dynamic libraries on every command afterwards. Setup is now redone automatically, and if it still fails the screen says what happened, that nothing in your account was touched, and what to do.
  • Teardown screens stop quoting the engine. bronco destroy from the wrong folder printed twenty lines of internal setup log with the real reason buried inside. Now it shows the failure, and nothing else.
  • Where your environment lives is on its own screen. The console names the folder, and the end of a build tells you the workspace path and where to run bronco from.
  • The commands on screen are the commands that run. If you started with npx bronco-cli, every suggested command says npx bronco-cli. If Bronco is installed, they say bronco.
  • An AWS service Bronco does not build is named before anything is built. If your app declares a client for something outside the catalogue — DynamoDB, Bedrock, Kinesis — the build says so first, and says what happens if you go ahead: an app that falls back silently loses data, and one configured by hand gets AccessDenied. Bronco creates none of them, and now it tells you.
  • Build-time values you can set. build.args in bronco.app.yaml reaches the image, so frameworks that bake configuration into the bundle (NEXT_PUBLIC_*, VITE_*) stop shipping their Dockerfile defaults. And a build says which ARGs the Dockerfile asked for that nobody supplied.
  • The account you connected with bronco doctor shows up in the console. It used to report that no account was connected, right after showing you a full report of yours.
  • The teardown and the check that verifies it now agree. bronco destroy says the bill stopped; bronco sweep verifies it. On a real teardown the two described the same resources in opposite terms — one hedged that a resource might already be gone while the other said it had outlived the teardown and offered a command to remove it. Both screens now read one verdict, which records for every leftover what it bills, whether Bronco MEASURED that it is still there or only saw its tag, whether AWS clears it on its own, and what you can do about it. No screen can say more than that verdict supports.
  • A teardown now records itself. A bronco destroy outside the region holding your account's state could finish, report success, and leave no record — so bronco sweep went on calling the environment live and bronco discover went on offering it. The write is addressed correctly, and if it cannot be made the screen says so instead of staying quiet.
  • Leftovers are named, and settled facts are not called provisional. The one line about tags AWS has not dropped yet listed nothing, so there was no way to check it against what sweep printed next; it now names what it counted, and no longer asks you to re-check something Bronco already confirmed is gone. A teardown that could not check something no longer shows a green tick over it.
  • A database backup that CAN be removed is no longer described as one that cannot. An automated backup kept past its database bills until its window closes and is removable with one AWS CLI call. Bronco was printing the sentence written for the other kind, and pointing at the AWS console, which cannot delete it.
  • Leftover log groups are visible again. Bronco's check for a CloudWatch log group could never answer "still there", so every live one was reported as already gone.
  • A teardown no longer waits on a service that cannot start. If a service's image cannot run, the teardown used to sit out the rest of its restart cycle. Bronco stops the cycle first, and only where nothing is running; if the teardown then fails, it puts back what it stopped.
  • --project names the environment the command acts on. Run from a folder holding a different project, bronco destroy --project <name> acted on the folder's environment and reported success over the one you named.
  • A second region is never chosen for you, and never hidden from you. If your project already stands somewhere, the question says where — and if it stands in two places, it says so rather than quietly offering a third. Choosing a new region now prints what that means before anything is built: separate network, database, cache and load balancer, no service reaching across, and a move later that is a teardown and a rebuild.
  • A build that fails partway says how many things failed AND how many reasons there were. Over a real failure, four resources refused for two reasons reported neither number correctly.

Added

  • bronco deployer upgrade no longer needs a browser. It brings an existing deploy identity up to the current permission set over the API, using your own credentials — the identity and its access key are untouched, and the previous permissions remain as a non-default policy version. The console route stays as the fallback, and the screen says which one ran.
  • bronco deploy --build local|codebuild. Without Docker on the machine, Bronco asks whether to build images in CodeBuild, because that adds to your AWS bill — and there was no way to answer in advance, so an unattended deploy from a machine without Docker was impossible. The flag is that answer. The cost notice still prints.

Changed

  • Connecting an AWS account leads with the path that needs no browser. Paste a key once and Bronco creates its own scoped identity with it, over the API; the key you pasted is used in memory and never stored. The CloudFormation route is still there as a choice, for anyone who would rather no key with IAM permissions passed through Bronco at all.
  • bronco watch on and watch off use your own credentials. The monitor is a Lambda function, and Bronco's deploy identity is deliberately not allowed to create one — so these two verbs now say so up front instead of failing partway through.
v1.1.1 September 2026

Fixed

  • bronco --help now prints the “a newer Bronco is available” notice. It is the screen most people read right after installing, and it was the one surface that stayed silent. bronco <command> --help is still side-effect-free, and bronco --version still prints nothing but the version, so scripts that parse it are unaffected.

Security

  • Fewer known advisories in the dependency tree: four down to one. The one that remains needs a breaking upgrade and is tracked.
  • A note on npm install. Installing Bronco prints a deprecation warning for a transitive package pulled in by the spreadsheet writer behind bronco report. It carries no security advisory. npm resolves a dependency’s version from the project doing the installing, so Bronco cannot pin it on your behalf; the standalone binaries are unaffected.
v1.1.0 September 2026

What an upgrade from 1.0.1 changes, in the order it is noticed: a deploy to dev no longer takes your app offline, a deploy that cannot start is reported in two minutes instead of eight, every deploy says what a visitor actually gets, bronco logs and its neighbours start in half the time, container builds are 4× smaller, and the console screens fit the terminal they are read in. Three figures Bronco had documented from arithmetic were measured against live AWS and corrected.

Fixed

  • Deploying to dev no longer takes your app offline. A dev deploy used to stop the running container before starting its replacement, and the gap was measured at 2m43s, 2m23s and 2m24s on three consecutive deploys — while the note explaining the choice said “a few seconds”. Dev now deploys the way staging and production always have: the replacement starts and has to pass its health check before the old one goes. Measured from outside the stack, twice a second: zero seconds of downtime. It costs one extra container for the two minutes of the rollout, about $0.0004 a deploy. Environments you already have pick it up on their next bronco apply, with nothing destroyed or replaced.
  • A deploy that cannot start is reported in two minutes instead of eight. When Amazon says a container failed its load-balancer health check, that is a verdict on the version you shipped, not a hiccup — Bronco used to wait for a second identical failure before saying so. Measured on a real stack: 2m03s to abort, against 7m30s. Your running app is untouched throughout — 2139 of 2139 probes answered normally across the whole failed deploy — so a deploy that is refused costs you a re-run, not an outage. A service running two or three containers still waits for a second failure, because one container of three losing a race is a deploy that is still working.
  • A new environment no longer emails you “nothing is running” minutes after you built it. The alarm was telling the truth — for the few minutes between the alarm existing and your first task reporting in, nothing was running yet — and it was the first thing a new user heard from Bronco. The alarm that reaches you now waits until the service has been seen running at least once, so it stays quiet through the build and still fires when a service that was running stops. It adds $0.50 per service per month, and the estimate says so.
  • bronco logs starts in half the time. Every lifecycle command was loading a spreadsheet writer and a PDF writer before doing anything. Measured: 1113 ms to 543 ms. Nothing about the reports changes — they load when a report is actually written.
  • Container builds were shipping Bronco’s own working directory. Your .bronco folder was travelling inside the build context, and on Windows that is a several-hundred-megabyte copy of the deployment engine, sent to a Linux container. Measured on a one-file app: a build went from 64 s to 4 s and the image from 196 MB to 46 MB; building in your own AWS account went from a 150 MB upload to nothing. Bronco adds the missing line to your .dockerignore and says on screen that it did.
  • A telemetry option nobody chose was billed on every environment. Amazon’s per-task container telemetry was on in every environment Bronco has ever built, decided by nobody and shown on no screen. Counted on a live two-service cluster, it publishes 63 metric streams — $18.90 a month, or $11.40 with one service. It is a choice now: off in dev, on in staging and prod, priced where you decide it, and reversible either way. An environment that already has it keeps it — nothing is switched off under a running stack. The “nothing is running” alarm no longer depends on it either: it reads a task count Amazon publishes for free, every minute.
  • Your tasks now say which CPU architecture they run on. They always ran on x86, because that is what Amazon assumes when nothing says otherwise — which meant a build from an Apple Silicon Mac produced an image the tasks could not start, and nothing on screen said the word “architecture”. Builds are now pinned to the architecture your environment declares, and a mismatched image is refused before it is pushed instead of failing halfway through a rollout.
  • The cost estimate was missing $26 a month of things AWS bills for. The pre-apply figure now names five more lines: the container telemetry above, the two public IPv4 addresses every internet-facing load balancer holds ($7.30/month, billed whether or not they carry traffic), the content-delivery distribution a public environment without a custom domain always gets, the NAT gateway in dev, and stored secrets, which grow with the databases and caches you name. The Fargate compute rate was also 1.9% below the real one and now rounds up. Your AWS bill does not change; the estimate stops understating it. It also names the free tier it cannot model: Amazon gives every account its first ten custom metrics free, which is $3.00 off the first environment’s telemetry line and nothing off the second.
  • The guided setup was over the AWS size limit for a permission document. The read permissions the free diagnosis needs pushed it past a hard 6144 characters, so it would have failed on any account. It is now two documents — what Bronco builds, and what Bronco reads — and a size check keeps it that way.
  • The security scan could not read the one thing it most needed to. A permission was granted narrowly enough that the check for administrator access reported “could not read a policy document” about the only account member that actually had it.
  • A capacity option the cluster advertised and could not use has been removed from the registration. Nothing that ran, ran differently.

Added

  • Your deploy now tells you what a visitor gets. A deploy can finish green while your app is down: the load balancer only ever asks the one path you told it to, so if that path answers and your real routes do not, everything in the pipeline agrees the release is fine. Every deploy now also asks the route your service is actually reachable on, and prints both answers — web — /ready answered 200, / answered 503. It reports, it never blocks. A root path that answers 404 is an ordinary API and is not reported as a disagreement.
  • Screens that fit the window they are read in. Bronco’s console runs commands in its own output pane, 56 columns wide on an ordinary terminal. Six screens were wider than that and had never been measured there: the line that closes a rollback or a worker check, the evolution preview, the block that closes provisioning, the operate cockpit, the remediation plan, and the list of what a change touches — that last one put every resource a service implies on a single 276-column line, on the screen whose whole job is to be read before you apply. All six now fold to the terminal they are printed on, from a 40-column split pane to a 200-column ultrawide.
  • Graviton is a question, not a hidden setting. Running your containers on ARM is about 20% off the compute line, and until now it could only be turned on by editing a saved file. It is offered in the advanced tuning screen, alongside every other priced default, and is still not the default.
  • Connecting Bronco to your AWS account no longer needs a trip to the AWS console. If the credentials you already have can create an IAM identity, bronco login — and bronco forge when it connects — offer to create the bronco-deployer identity for you, and then work as it. Measured end to end: 12 seconds, no browser. Permission is checked before anything is created, and a failure leaves nothing behind. If your credentials cannot do it, the existing guided setup is unchanged and is where every failure lands.
  • bronco logout can now remove that identity from your account, after proving no environment in that account is still running. Until now Bronco could leave an identity behind that no command took out.
  • A third answer for “I don’t have an AWS account yet.” That situation used to end in three rejected credentials and an authentication error — a message about a wrong key, for somebody whose problem was the missing account. It now says the true thing: opening one takes a card, a phone number and an identity check, and it is not something Bronco can do for you.
  • A failed rollout says what is still happening. When a deploy is abandoned early because the platform has already judged the new version, the failure screen now says what did not stop: the rollout keeps running until AWS rolls it back on its own, roughly fifteen minutes in.

Changed

  • How fast a broken app is noticed, measured. If a service that was serving starts failing its health check, the load balancer takes it out of rotation in 22–29 seconds — measured three times on real AWS, not calculated. Bronco’s own documentation had carried a figure three times larger, which turned out to describe something else entirely: bringing up a replacement task, which takes about two minutes. Nothing changed in how your environment is built; the number it is documented with did.
  • What actually happens when a container never answers its health check. A note in Bronco’s own infrastructure said such a task is killed “every ~90 s”. That number was reasoned, not measured. Measured against a live stack, twice, on six tasks: the kill lands 63–69 seconds after the container starts, and a new task is launched about every three minutes. And it does not go on forever — both runs launched exactly three tasks before the deployment gave up, at 7m49s and 8m47s.
  • One address for your app, everywhere. If you gave your environment a custom domain, some screens showed you the load balancer’s raw AWS hostname instead — and bronco apply showed only that one, with your own domain nowhere on the page. That address does not work in a browser: it redirects to HTTPS on itself, where the certificate is issued for your domain and the name does not match. Every screen that names your app now names the same address: your domain when you have one, and the load balancer underneath it labelled as what it is. Environments without a custom domain are unchanged.
  • Cold start, measured. A container Bronco builds is given a 60-second grace period before its first health check counts. Nothing Bronco builds comes close to running out of time on its first attempt.
  • bronco replicate re-derives container telemetry at the target environment’s own ceiling instead of copying the source’s, so a copy of a dev environment reaches production instrumented.
v1.0.1 September 2026

Fixed

  • The update notice works. Bronco checks the npm registry once a day and, when a newer version is published, prints the command to install it: npm install -g bronco-cli@latest. It never updates itself — a tool that provisions infrastructure must not change version underneath a plan you are about to apply, so it tells you and stops. If the check cannot reach the registry it stays silent and gets out of the way, and it no longer treats a failed lookup as an answer worth caching for a day.

Added

  • The npm page links somewhere. bronco-cli now declares its homepage and where to report a failure, so the package page points at this documentation and names an address you can write to.
v1.0.0 September 2026

Added

  • Bronco is on npm. npx bronco-cli doctor reads any AWS account you already have — free, read-only, no sign-up — and npm install -g bronco-cli keeps it. The package is bronco-cli; the command it installs is bronco. The short name on npm belongs to an unrelated package, and the command you type was never going to change for that. A pipeline written by bronco ci generate installs the same package pinned to a version, so publishing a Bronco release never changes what your production pipeline runs.
  • There are no downloadable binaries in this release. Every install goes through npm. The three download buttons this site used to show pointed at release assets nobody outside the project could reach, so they are gone rather than decorating a 404. Binaries are still intended; they are not promised anywhere until they exist.

Fixed

  • Two AWS SDK clients were missing from the package's dependencies. Cognito and Lambda were listed as development-only while shipped code imported them, so a fresh install would have failed on the first command. Found by installing the built package in a clean container and running doctor — the project's own checkout has every development dependency present, which is why the full test suite, the binary build and the certification run all passed over it. A release check now walks everything the package ships and fails if it imports anything that is not a runtime dependency.
  • No AWS account number is published any more. A real one sat in a copy-paste CI example on this page and in two source comments that rode along in every published copy. They are placeholders now, and a release check rejects any twelve-digit account number in anything that ships.
  • Every figure on the landing page comes from a measurement or a count. The screen at the top of that page had been transcribed from a document written when the check catalogue was smaller, so it advertised 47 checks two sections above the page's own count of 58. Catalogue numbers are counted from the catalogue itself now, and terminal screens are generated from committed captures of real runs.
  • The annual billing toggle is gone. It showed an annual price and kept the monthly checkout behind it.

Added

  • A failed deploy rolls itself back. When a new release cannot serve, Bronco puts the last working release back on its own — no keypress, the same in CI — and tells you what the URL answers. Measured on a one-task environment: the outage went from 7m35s, most of it a person reading the screen, to 4m39s with nobody at the keyboard. The deploy still fails, both events land on the timeline, and --no-rollback keeps the broken release up when you want to debug it live.
  • A worker that does not work is not a ✓. The Dockerfile Bronco generated for a worker used your app’s start script — the API’s — so the worker ran a second web server and never consumed its queue. A worker’s command now comes from its role (a start:<name> or worker script, or a worker.js file in the source folder), Bronco asks when it cannot tell, a worker whose container equals a public service’s is refused before anything is built, and after the rollout Bronco reads the worker’s queue and says what it measured: consuming, nothing waiting (so not checked), or not consuming — which fails the deploy.
  • destroy can delete the audit-trail bucket too. Switching CloudTrail off left a versioned bucket only a script could delete. The teardown now offers it with the count — “30 objects in 30 versions, ~224 KB” — and deletes every version if you say yes. The default is no: those are your audit records.
  • bronco run "<command>" and bronco db "<sql>" — execute something against your own app. Until now there was no way to run anything inside a deployed environment: your database is on a private network with no route from your laptop, so when a migration failed and left the schema blocked, the way out was the AWS CLI. bronco run runs one command inside your app — same image, same secrets, same network — and bronco db runs SQL against the environment’s own database, with the password never reaching your machine. Both work in production, where they ask first, and neither is part of a paid plan: getting unstuck is never something you pay for. A migration that blocks your schema now prints the exact command that clears it.
  • The timeline records the things that change your account. rollback, alerts set, watch on, pause and resume all changed something in AWS and left no trace. A rollback is the worst one to lose — the moment you open a history is the moment after an outage, and the recovery was the half that was missing. All of them are recorded now, the rollback whether it worked or not, along with generating a CI pipeline.
  • Name your own bucket, queue and topic variables. Bronco injects your queue’s URL, your bucket’s name and your topic’s ARN, and it used to choose the variable names for all three. If your code already reads JOBS_QUEUE_URL, you can say so now — the same question Bronco already asked for databases and caches, pre-filled from your own repository. The standard names (S3_BUCKET, SQS_QUEUE_URL, SNS_TOPIC_ARN) still arrive alongside, so nothing already running changes.
  • The last teardown offers to switch the account controls off. The audit trail and threat detection are account-wide, Bronco turns them on, and nothing in the product turned them back off — leaving meant a trip to the AWS console. When the last environment that needed one goes away, bronco destroy now offers to remove it: one question per control, never under --yes, never without a terminal, and declining changes nothing.
  • bronco forget retires the CI role bronco ci generate created. The role is per-project, costs nothing — which is why no cost screen ever named it — and kept granting deploy access to a GitHub repository long after the project was gone. bronco forget now offers to delete it, as its own question, naming the repository that loses access. Your account’s OIDC provider stays: it is shared by every project. If the role cannot be read, that is reported as a gap, not as “there is none”.
  • An adopted environment can be handed back — and then forgotten. Adoption records that an environment relies on the account-wide controls Bronco manages, so tearing down a different environment can never switch off something this one still needs. Nothing ever took that record back, so every teardown in the account said the audit trail and threat detection were still needed by an environment that no longer existed — and threat detection went on billing for it. bronco adopt undo now finishes the job: it removes the imported entries from Bronco’s saved state (no AWS resource is touched), releases the account-level claim, deregisters the environment, and hands you to bronco forget, which now accepts the folder instead of pointing back at adopt undo. The two verbs used to refer to each other with no way out. The saved state is read rather than inferred, and a hand-back interrupted partway through resumes rather than restarting.

Changed

  • The health gate now works from CI, and from a teammate’s machine. bronco doctor --gate blocks a deploy when your environment picked up problems it did not have when it was last known good. The floor it compares against used to be a file on one machine, in a folder that is never committed — so a CI runner found nothing to compare against, recorded whatever it saw, and passed. Every push. And two developers held two different floors for the same environment. The floor now lives with the rest of your environment’s state, in your own account’s state bucket. A floor you already accepted on your laptop is carried over the first time you run the gate, when it is that project’s and does not predate a teardown. A floor that cannot be read stops the gate rather than quietly passing it, and so does a floor that could not be saved: a check that warns and carries on is a check that never blocks.
  • The number on the console says what it measures. Your board shows one score per environment. It is the three grades — security, reliability and cost — weighted together, and it equals none of them, which made it look like a fourth grade nobody explained. The breakdown now shows the weighting it actually used, names any family that had no data and is therefore not counted, and states drift’s penalty separately — so the numbers on screen add up to the number on screen.
  • A scan run inside a project is about that project. bronco doctor --env dev from your project folder answered “this scope spans 131 projects”, because dev is a name every project in an account has. The guided console, standing in the same project, got this right — so the two doors gave different answers to the same question, and the generated CI pipeline inherited the wide one. Run from a project folder, a scan narrowed by environment or service now means that project, and says so on screen. A plain bronco cost still covers your whole account: the free diagnosis is account-wide by design. Naming --project yourself always wins.
  • bronco simulate only offers changes that cost something. On a modern environment the picker offered three changes that could not move the number — and printed $0.00/mo as if it were a forecast. It now offers only changes this environment can actually make, checked by pricing each one first — including inside the follow-up questions, so a service already at its top criticality is not offered a raise that changes nothing, and one below the top is offered every step that does. Adding a database, a cache or a bucket, and raising a service’s criticality, are new options.
  • The generated pipeline says what it does not do. bronco ci generate deliberately leaves the health gate out: it reads your whole account. Every generated pipeline now says so, and says exactly what running it would take — including the one write it makes, since it records the floor it blocks against. Where Bronco created the credentials the file says what they hold; where you supplied them, it says it cannot see their scope rather than guessing.
  • The infrastructure report describes what you actually have. Queues, buckets and topics are on the Endpoints sheet with their addresses, and a new section lists the environment variable each of your apps reads them from. The secrets row describes how values reach your containers today — your platform resolves and injects them at start-up; your app does not have to fetch them. And a report for an environment sized by per-service criticality no longer prints fields from the older sizing model.
  • One name means one thing. A database, cache, bucket, queue or topic name has to be unique across all of them, because your services connect to them by name. Two resources sharing a name used to wire one app variable to the wrong resource, silently. That configuration is now refused when it is about to be built, with the two resources named — and never when you are reading, tearing down or rolling back what already exists.
  • bronco sweep measures the account controls instead of repeating itself. The account-level section printed what Bronco recorded when it turned the controls on, so a control you deleted by hand kept being reported as billing. Each control is now checked against AWS and the row says which answer it is — confirmed, suspended and not billing, or on record because AWS could not be reached. A control AWS says is gone leaves the record, and the screen says so rather than letting the row disappear. Controls that were already yours before Bronco arrived are never guessed at.
  • bronco adopt stops offering Bronco’s own infrastructure, and AWS’s. The delivery bucket for the account audit trail is Bronco’s own and is no longer offered for adoption; it also stops counting against your audit grade, and is listed and marked as Bronco’s rather than hidden. A VPC’s default security group and main route table are never adoptable, whoever owns the VPC: AWS creates them with the VPC and will not delete them, so an adoption could never hand one back cleanly. Leave a resource out is now a menu row of its own — it was always possible, buried inside “Move a resource”, and read as all-or-nothing. And adopt scan and adopt plan now agree: both name everything they left out and for which reason, where the plan screen could previously report “nothing adoptable” one command after the scan reported several.

Fixed

  • bronco drift no longer accuses your own deploys. A deploy’s secret upload is Bronco’s own act, not a change made outside Bronco. After bronco watch on, bronco apply or any other Bronco apply, the next check says which command re-applied the stack and when, keeps reporting a change it had already reported if the apply did not cover it, records what is new since the apply and says so — it never calls itself “a first check” again. Removing a rule Bronco never declared is reported as a cleanup, not as drift. The watch digest says “not re-checked since … re-applied the stack” instead of repeating a count from before the apply.
  • bronco destroy no longer asks you to delete an automated snapshot AWS is already removing. It says the backup goes with the database, within minutes, and bills nothing after that; a backup you chose to retain is named as billing, with the command that removes it.
  • The console of another machine learns about a teardown. The console, bronco projects and bronco outputs read the account’s record before they believe a local stamp; an environment torn down elsewhere shows “torn down from another machine” with the date. No credentials are ever prompted for, and offline the screens paint as before.
  • bronco releases names which service is on a release (“← live: worker”) and says “← live now” only when every service runs it; bronco operate status keeps every service’s last recorded image after a --only deploy.
  • bronco doctor --env names the account as its subject and adds how much of it is the environment’s; a load balancer created minutes ago is not called idle. The watch digest grades the environment’s own resources, says how many findings elsewhere in the account it left out, and a restore copy on hold is listed, never counted as a reliability regression. The savings figure now carries its move since the last digest.
  • A deploy’s cost line is the estimate, named as one. The account’s real 30-day spend is shown beside it under its own name. The environment list and the console’s header stop printing a weighted number bronco doctor never prints; the room still explains it.
  • The outage screen asks your health path, the same one the load balancer asks, and never says “still serving” about a service with no running task.
  • bronco outputs during a build says a build is running, since when and by whom, and marks what it prints as partial. The DB name row says it is Bronco’s default for the database inside the instance you named. Every container gets NODE_ENV=production in every environment — Node’s meaning of NODE_ENV is “deployed” — and BRONCO_ENV carries the environment’s name; outputs lists both.
  • The console opens — and a console that fails never fails your build. bronco with no arguments crashed on its first screen, and answering Yes to “Open dev in the console?” at the end of bronco forge turned a correct build and deploy into “Command failed”. Both are fixed; if the full-screen console cannot open, Bronco reports it and opens the plain console instead, and no error prints an internal stack trace any more.
  • plan --out warns before it publishes. If you are the only identity on the account, a published plan is one nobody can approve or apply. It now says what the artifact will need and asks whether to go ahead; declining names bronco apply.
  • restore db tells you the price and asks. A restore creates a second database that bills until you commit or abort. Bronco asks first, with the class, the storage and the monthly and daily figure; --yes is the only way past the question in a script.
  • An environment you handed back is no longer a stack you pay for. An adopted environment returned with adopt undo kept showing in operate status as a live stack with a monthly figure. Every screen that reads the environment registry now reads the same verdict: the cockpit drops it, sweep names what is live and counts “handed back” apart, and discover says how many it hid and why.
  • The spending brake projects one kind of number. “Projected $98 vs cap $100 (98%)” with one cent spent added a monthly estimate to a month-to-date total. The projection is now what this month will cost if the plan is applied, and the line says which number is which. An apply with no changes no longer warns about the cap or asks for confirmation.
  • An apply onto a paused environment says so. alerts set, apply, watch on and a deploy that provisions new services tell you when the environment is paused, what stays stopped, and — when the change touches the database or a service — that AWS may refuse it until bronco resume.
  • Your health grade says whose it is. bronco doctor has always checked the whole account, but the grade was also filed under whichever environment you scanned from, where it stayed — so a scan run while another environment was still being built could leave that one’s half-finished load balancer showing under the first one’s name all day. There is one grade per account now, and every screen reads it from there.
  • Bronco stops counting itself against you. Its CloudTrail bucket now keeps version history, so a deleted log file can be recovered rather than only detected. Its monitoring function now reports its own failures to your alerts address instead of going quiet — which is what everything being fine looks like. And the leftover log groups a teardown leaves behind, which every screen told you to delete and no command could remove, can now be cleared by bronco fix.
  • bronco fix plans over every kind of finding. It only ever looked at security ones, so a cost or reliability fix it offered on screen could not actually be run.
  • bronco resume brings the environment back, and proves it. Resuming used to trust a note Bronco left on your laptop rather than looking at your account — and when AWS was still shutting the database down, the restart was refused and reported as success. The result was the worst state an environment can be in: tasks running, database stopped, URL returning 503, and a second bronco resume answering “this isn’t paused”, with no command left that could start the database. Measured on a real environment, that lasted 25 minutes before the way out was the AWS CLI. resume now reads your account first: it waits for a database that is still shutting down, then starts it; it waits for the database to accept connections before the compute comes back, and if the database does not return your services stay at zero tasks on purpose, because compute running against a database that is down bills in full and serves nothing. Every line it prints afterwards is a number it re-read from AWS, and it works even if the pause note is gone. --no-wait issues the orders and reports, for scripts.
  • bronco pause tells you what it does not stop. Pausing stops your compute and your database. It does not stop your network, your cache, your load balancer or your CDN — on a real staging environment that was about $45/month of a $143 bill, and the screen showed only the saving. It now shows both halves, split out of the same cost breakdown every other screen uses, and names bronco destroy as the only thing that stops the rest.
  • bronco fix checks that the fix took. Bronco used to report “Fixed” as soon as AWS accepted the change. Several of these changes — attaching a permissions boundary, starting an audit trail, enabling threat detection — are accepted before your account reflects them, so a green tick could stand over something that had not happened yet. Every remediation that can be wrong about this now re-reads your account and says which of three things is true: fixed and confirmed, applied and still reported, or applied and the check itself could not read. The third is never shown as a success.
  • Secrets you manage yourself. If you tell Bronco your secrets are already in AWS, it does not upload them — and it used to say nothing more, while still creating the variables empty, so your container started with them defined and blank and failed immediately. The deploy now reads the secret and tells you which of your declared keys hold a value and which are empty, before you go looking through logs.
  • bronco discover writes the project identity, and says what it reconstructed. A teammate reconnecting to an existing environment now gets the project’s own identity file — the one bronco new asks you to commit so the name travels. It was never written by discover. The bronco.app.yaml it produces is rebuilt from the saved infrastructure configuration, not from your repository’s deployment contract, and the old wording read as if it were the same file. It is now labelled a reconstruction, in the file and on screen; an existing contract — including bronco.app.<name>.yaml companions — is found and kept; and the screen no longer suggests a committed contract will override it, because deployment contracts merge by service name and the two would collide instead.
  • Three corrections in the guided console. Plan labelsforge, deploy, evolve and apply were labelled “Needs the Developer plan” even when they were free, since your first dev environment is free forever and all four ran; they are priced from your account’s real state now, and when Bronco cannot tell it says nothing rather than guessing. It refreshes — the board used to be a photo taken when you opened it, so after a teardown from another terminal it still showed the environments and their cost; it re-reads as you move, and r re-reads without moving. Whose health it is — Bronco’s checks cover your whole account, and the console was showing that verdict under one environment’s name, so a finding about an environment still being built showed up as another environment’s problem.
  • Five more checks that gave up on your whole account over one resource. If Bronco could not read one IAM user, one Bronco role or one NAT gateway, five checks answered nothing at all about the rest of the account — including the check that reports a user able to take over everything. All 58 checks were classified for this: 26 behaved the same way, 28 now report what they could read and name what they could not, and nine still refuse to answer partially, deliberately. Measured on a real account, denying read access to exactly one bucket out of twelve took the two S3 checks from 17 findings to 0; fifteen of those were about buckets that read perfectly well. The same account now reports 15 findings and names the one bucket it could not read.
  • The monitor Bronco installs in your account was running an old copy of the checks. The copy that shipped was two months behind the checks Bronco itself runs — it carried 12 of the 58 — and its permissions had never been widened to match. The digest now says how much of the catalogue actually ran, and a release check reads the published monitor and fails if any check is missing from it.
  • bronco watch stops saying “in sync” without checking. The weekly digest reported no drift on environments where drift had never been checked. A check that has not run now reads as “not checked”, because an absence is not evidence.
  • bronco releases after a teardown. Releases from before an environment was torn down are not offered — their images went with the stack. The screen said nothing had ever been recorded; it now says how many are not being offered, and why.
  • bronco adopt can adopt a plain VPC and subnet. Bringing existing networking under Bronco failed on a schema mismatch in the configuration the AWS provider generates from your own resources. Bronco now repairs what the provider will not accept, asks the engine to confirm the result, and checks that every resource you asked for is actually described before it imports anything — so a partial read-back stops with nothing recorded rather than halfway through.
  • bronco sweep no longer bills you for something you deleted. Threat detection deleted by hand kept appearing under the account-level section at $3–10 a month. Sweep now checks with AWS.
  • The console keeps up with your plan. Activating a licence from inside the console left the plan badge showing your old plan until you quit. It refreshes now.

Security

  • bronco destroy, bronco deploy and bronco evolve refuse an adopted workspace. destroy could be aimed at one, and would have torn down a customer’s own VPC, database and load balancer. It now refuses before it even connects to AWS, and points at the hand-back. deploy and evolve say what an adopted environment is in adoption’s own terms — it brings existing infrastructure under management for drift, report and cost; it does not describe services or build a place to run your app.
v1.0.0 August 2026

Added

  • bronco alerts set ops@acme.com --env prod — the address your alarms go to, on an environment that is already running. Until now the question could only be answered while the environment was being built, so one built without an address kept its full set of CloudWatch alarms publishing to a topic nobody was subscribed to, and the only way out was an AWS command typed by hand. bronco alerts on its own shows who is receiving them today. It is never gated — a lapsed licence must not silence a safety question, the same reason teardown and rollback are free. It also says what it cannot do: AWS emails a confirmation link and somebody has to open it, so until that happens the screen states plainly that an alarm which fires still reaches nobody, rather than reporting success. And it changes only the one thing: if your environment has other changes waiting, it refuses and lists them instead of applying them under the word “alerts” — which, on the environment this was built against, caught a plan about to remove security guardrails from five roles.
  • bronco discover --project <name> --env <name> reconnects without a terminal. A fresh clone, a new laptop or a CI runner with no local workspace can rebuild one from your own AWS account and carry on. Asked bare it still walks you through the choice; naming the project and environment makes it scriptable. If what you name is ambiguous it refuses and tells you which flag would settle it, rather than guessing at your infrastructure.
  • bronco forge --example prints a complete, valid answer file for the headless build. Bronco can build and ship an entire environment with no prompts — for CI, for scripts, for agents — and the feature worked while being close to impossible to discover: bronco forge --help listed only --dry-run, and what goes inside the --config file was written down in exactly one place, which shipped with the source and not with the binary. Redirect the example and you have a working config: bronco forge --example > bronco.forge.yaml, then bronco forge --config bronco.forge.yaml. Every key is in it, the required ones live and the optional ones commented, including the two that are easy to get wrong — domain and alerts need an explicit null to mean “none”, because a missing key means “nobody said”. The help lists both flags, and the docs carry a new page for the whole headless path.
  • The review shows which service answers on which URL, before anything is built. The first public service in your contract answers on /; every other one answers at /<name> and /<name>/*. That single rule decides whether your frontend or your API gets the root, and it appeared nowhere a reader could find it — so writing the contract the natural way, backend first, quietly put the API on the root and the frontend under /web/. Each public service now carries its path in the plan recap. Put your frontend first and your API second, and the browser reaches both on one origin with no CORS.
  • A health check that can never pass is caught before the deploy, not three minutes into it. The load balancer keeps the path when it probes your container, so a service reached at /api/* whose routes live under /api needs health: /api/health. With the default the target never turns healthy and the rollout fails with a message about ECS tasks — a sentence that points at Docker rather than at the one line that caused it. Bronco now names the line to change before applying anything, and names the same cause if a rollout does fail. It warns rather than refuses: an app may legitimately serve /health at its root while routed under a prefix.
  • Public services scale on request volume, not only on CPU and memory. A measured load of 703 requests per second, sustained for thirty minutes, held CPU at 32% and memory at 4.3% while the fleet stayed at exactly 2 tasks against a ceiling of 6 — the two existing policies watch the two things an I/O-bound service does not run out of, so the ceiling was decoration. A third policy targets 1200 requests per target per minute, derived from ten in-flight requests per task converted through a 500 ms request, with 60 seconds to scale out and 300 seconds to scale in. Workers do not get it: a worker has no requests to count, and a scaling policy that silently never fires is worse than none.
  • Every database now carries an alarm on how close it is to its connection ceiling, which nothing was watching. In a measured incident, six tasks holding a 50-connection pool exhausted a database that accepts 79 connections: two thirds of every request failed for four minutes, and of the ten alarms in the environment exactly one reacted — the load-balancer 5xx alarm, at 2m11s, which reports that requests are failing and nothing about why. Database CPU sat at 99.2% and its own alarm stayed OK, because it needs five consecutive breaching minutes and the outage lasted four. The new alarm fires after two minutes above 80% of the ceiling, and each database gets a threshold matched to its own size rather than one number for the environment: a stack with two differently-sized databases planned thresholds of 126 and 252, and 22 alarms in total. A database size Bronco has not characterised gets no connection alarm rather than an invented threshold. One caveat: during that incident CloudWatch reported 260 connections while the database was refusing at 79, so the alarm fires early rather than late, and the number on the graph is not a count of accepted sessions.
  • Alarms now cover every service, database and cache in an environment instead of one representative of each — a worker in a crash loop raised nothing before, because no running-task alarm watched it and it has no requests to turn into errors. A three-service environment plans 20 alarms, each pointed at its own resource. A new alarm watches the 5xx the load balancer returns itself, which the per-target alarm cannot count: a service with no healthy target receives no requests to fail. The existing alarms are kept exactly as they are, so an environment that already runs has none destroyed.
  • A new alarm fires when a service serves errors while every target reports healthy. That combination is what the failover incident looked like for fourteen minutes, and it is the one reading that says the health signal itself cannot be trusted. One alarm per public service, measuring the errors and the health on the same target group.
  • bronco risk reports alarms that have been stuck in one state for more than 24 hours. An alarm that has been firing for a day has data it cannot clear and will notify nobody about the next incident; an alarm with no data at all will not fire whatever happens, and every screen counts it as coverage. The two get different sentences, and the check catalogue is now 48 checks.
  • bronco destroy and bronco outputs work from a machine that never created the environment. The saved configuration and state live in the account's state bucket, but both commands read only the local workspace folder, which is gitignored — so a lost laptop, a fresh clone or a clean CI runner left the owner of a live environment with no way to switch it off, and no way to reprint its endpoints and connection details. Both now rebuild the workspace from the state bucket, and both accept --project <name> beside --env <name>. The environment is identified from the caller's own AWS credentials, then the project, then the environment; more than one candidate and no way to choose is a refusal that names the flag to pass, never a pick. An account that is not already connected is never asked for credentials.
  • bronco apply now asks the account what it will refuse before the first call that bills, and exits 2 with the reason and the remedy before the confirmation prompt, so nothing is created and no plan is approved that the account has already rejected. Two things are checked. The region's Elastic IP quota against the number of NAT gateways this environment builds: each gateway takes one, the default regional ceiling is five, and a second production environment walks into it — measured against a real account at limit 5 with 1 allocated, two and three gateways pass and five is blocked with "needs 5, 4 free of 5". And any value this account has refused before, recorded per account with AWS's own message kept verbatim. A previous apply failed twice about seven minutes in, with roughly forty resources already created and charging; the same refusal now stops in seconds with nothing created. No AWS service reports an account plan restriction before the create call, so the first encounter still costs its seven minutes. A check that could not be read is reported as a gap, never as a pass.
  • The pre-build check now asks about six ceilings, not one. Beyond the region's Elastic IPs it reads VPCs, internet gateways, Fargate on-demand vCPU, database instances, load balancers and cache nodes, comparing each against what this environment will actually build and what the region already holds — and it subtracts what the environment already owns, so rebuilding or growing one is never refused for resources that are already there. The Fargate ceiling is measured against the number of tasks scaling can reach rather than the number it starts with, because that failure is silent: the build succeeds and the scaling quietly cannot happen. Ceilings come from the account's applied quota rather than the published default — on the reference account those differ by five times. A ceiling that could not be read is reported as unchecked, never as passed, and the two it deliberately does not check are named rather than left as silence. It now runs when you create an environment as well as when you rebuild one; it had never run on the command that actually creates the network.
  • A value your account refused is no longer remembered forever. Bronco records what AWS rejected so the next attempt is refused in seconds instead of seven minutes into a build with resources already charging. But an account's restrictions change: four of them stopped applying within a minute of a plan change on a real account, and a refusal recorded just before would have blocked a build that works. Each refusal now carries the account plan it was recorded under. The same plan still blocks; a changed plan, or one whose own expiry has passed, is forgotten and removed; a plan that cannot be read informs instead of blocking. And a build that succeeds forgets whatever it just disproved. There is no timer, because any interval would be a number nobody could defend.

Changed

  • Four checks could not tell “nothing found” from “could not look”. A read that fails and a read that comes back empty are not the same fact, and four checks treated them alike. Each was proven by taking one permission away from a read-only identity and running the scan for real. With the bucket-policy status read denied, a genuinely public bucket was reported as “missing Block Public Access” instead of “S3 bucket is public” — a true sentence about a bucket whose worse truth nobody could see. A bucket whose region could not be read left the version-history report entirely, and the scan still said nothing was missing. A denied read of either API Gateway generation produced the same empty list an account with no APIs produces, so “no APIs without access logs” was reported on evidence nobody could gather. And when the account’s region list could not be read, --all-regions quietly covered one region and said nothing about the rest. All four now say what they could not read, as a coverage gap naming the permission. With full permission the findings are exactly what they were before. Every remaining place a read is deliberately allowed to fail quietly is written down with the reason it may, and a check that adds a new silent one fails the build.
  • The last release was reviewed again, from scratch. Its independent review could not run, so its own author reviewed it. Read cold, four defects turned up in that day’s work, three of them inside the checks written to guard it: a detector that dismissed the very pattern it exists to find, a fix that could be undone with the whole test suite still passing, a rule that accepted the exact case it forbids once the braces moved, and the project’s own reference document shipping six paths that do not exist while its guard reported success. All four are closed, each with a permanent test that fails if it returns.
  • The security audit runs about twice as fast. Two of the IAM checks asked AWS about one user at a time, waiting for each answer before starting the next. On an account with 22 IAM users they were almost the entire scan. Measured end to end, the audit went from 11.3 seconds to 5.3 seconds — asking AWS exactly as many questions as before, and reporting exactly the same findings. Accounts with more users gain more, because the wait was proportional to how many you have.
  • Two checks no longer guess when AWS does not answer. If the call that reports when an access key was last used failed, the key looked like one that had never been used at all — which is the more serious finding — so a failed read could invent an alert about a key in daily use. If the call that reads a user’s inline policy failed, that user simply looked like they were not an administrator. Both now say they could not check, instead of answering. A read that did not happen is never reported as a clean result.
  • Three security checks now read your account before advising it. Each was measured against a real AWS account first, and the measurement changed what was expected: only one of the three moves your grade. MFA on IAM users — every user without an MFA device was flagged, whether or not that user can sign in to the console at all, so a CI identity with an access key and no console password was told to protect a sign-in page it cannot reach. Bronco asks now. On the account measured, both IAM users are programmatic-only and both were flagged: two findings disappear and the security score goes 48/100 to 50/100. Nothing is lost — an old programmatic key is still reported, and so is an over-broad one. The permissions ceiling on Bronco’s roles — the finding decided from a proxy and told you to run bronco forge to create a policy your account already had, while quietly withholding the one-click fix that would have capped the roles; it checks now, says the true thing, and offers the fix. No finding appears or disappears, so your grade is unchanged. Unencrypted disks — the advice offered “enable default encryption for new volumes” without checking whether you already had, and it would not have helped that disk anyway, because the default does not reach backwards over volumes that already exist. Each of the three reads answers in three states, and the third one matters: when Bronco cannot make the read it says so and keeps reporting the finding. “I could not check” is never shown as “this is fine”.
  • The MFA check got faster while asking twice as much. It used to ask its questions one user at a time. Measured on an account with 22 IAM users: 7.2 seconds down to 2.4 — and it only asks the second question about users who failed the first.
  • Advice is read off your resource, never off its name. Six pieces of remediation told you to do something you had already done, because the check picked its words without reading the setting it was advising about. The one that started it: a production environment runs one NAT gateway per availability zone — that is what Bronco builds for it — and the cost screen then said “Production wants one per availability zone.” Advice already satisfied, printed twice. It now measures the zones your gateways are actually in, which is what the sentence is about; two gateways in a single zone is the case that sentence was written for, and it says which zone. Nothing here reads your environment’s name: how much redundancy an environment gets is your answer to “what happens if this goes down for ten minutes”, so an environment built for zone redundancy is never told to remove it. The other five: a load balancer that already has HTTPS was told to add HTTPS, directly under a line saying HTTPS was configured; a user pool was told to turn on all three sign-in protections whether or not it already had them, including the one that costs $0.02 per active user; snapshots already on the archive tier were told to move to the archive tier, and were priced at four times what archive actually costs; a network was told to add endpoints it already had, and was told its image layers travel free through an S3 endpoint that, in some accounts, was not there — the opposite of what the same screen said two sections above; and a cache that fails over on its own was described as needing a human to recover. Nothing new is flagged and no grade changes. One remediation also pointed at a command that does not exist on its own, and a new check makes that shape impossible to ship again.
  • bronco doctor lists everything it decided not to score. Some findings are listed but kept out of your grades — Bronco’s own defaults for dev and staging, Bronco’s own machinery, and leftovers from an environment you tore down. That list covered two of the three families of checks. The third, the money checks, had exactly one such finding — a dev environment’s NAT gateway, which Bronco builds on purpose — and it fell between the two: not scored, not listed as unscored, just counted in the money column and printed as that column’s example. It is now derived from the same place the counts are, so the list cannot miss a family. On a real account the change moved exactly one finding and no grade: security 48/100 and reliability 100/100 before and after. Three screens now agree with that count — bronco cost lists the unscored ones in their own section with the reason, the way audit and risk already did; the --out report gains a “Found, not scored” section instead of dropping them; and a scan that finds nothing but unscored items no longer opens with “Nothing turned up.” above a list of what turned up.
  • A command a screen offers now answers the question that screen asked. When you narrow a scan — bronco cost --project acme --env dev --service api — the screen tells you what it deliberately did not count: only what is tagged api; the network, database, cache and load balancer it uses are the environment’s — see them with …. That sentence exists to stop you reading a small number as good news, and the command it offered dropped your project. It said bronco cost --env dev, and --env dev is every project’s dev: measured on a real account, running it covered 132 projects and added another project’s NAT gateway to the bill. Every command these screens offer now reproduces the scope it is offered from — the --full pointer, the --why door on bronco doctor, and each suggested next step. bronco fix --dry-run is the one that mattered most: it plans a change, and an unscoped plan under a narrowed heading is a plan for the whole account.
  • bronco sweep --service now says what it did not count, like the other five scans. It is the one scan that loses no coverage when you narrow it, which is exactly why the line was missing — but what it loses is not coverage, it is the subject: a seven-resource answer under a service’s name reads as everything that service runs on.
  • A mistyped scope gets the same answer everywhere, and it names the name. Typing --project acmee used to produce three different behaviours depending on which command you typed, and the most helpful one was the command with the least information: the environment picker reads a folder listing, while the scans hold your whole account’s tag inventory and never said this account has --project acme. Now they do — one sentence, one rule, in all six places. It stays quiet rather than guessing: two candidates equally close to what you typed produce no suggestion at all, because dev1 and dev2 are how people actually name environments and a coin flip printed as an answer is worse than nothing. And run the account-level scan to see them is now the command itself.
  • Leftovers from a teardown stop being scored as risks. Six findings, six reliability points off the grade, and one remedy that could not work: log groups belonging to environments Bronco had torn down months earlier. The screen asked for retention to be raised on stacks that no longer exist, and running that would have paid CloudWatch to keep six empty groups for longer. A log group whose workload is gone is a leftover, not a risk — nothing will ever write to it again, so nothing about it can fail. It is still listed, under Left by a teardown, with the only honest remedy: delete it. On the account this was measured against, reliability went from 77/100 (B) to 100/100 (A) with nothing changed in AWS. If Bronco cannot read the account’s cluster list, nothing is set aside — a permission gap must never look like a clean bill of health.
  • bronco drift reads like the rest of the product. It was the one screen with no verdict, no severity, no consequence and no action: a list of bullet points and the word “changed”. A tag someone edited in the console and an SSH port someone opened to the world arrived at the same weight, on the screen whose entire purpose is to report changes nobody announced. It now opens with a sentence, sorts worst-first into the same bands bronco audit and bronco risk use, says what each change means, and says what to do about it — in both directions, because a console change is either a mistake to undo or a decision to keep. The first check on a stack no longer spends thirty-nine lines listing the differences AWS maintains for itself: it says how many, which kinds, and offers the list. Three things the live run found that no test did: one change to one security group was reported as two findings at two severities, because two detectors saw the same console action; a resource deleted in AWS lost its name entirely, when the identity of a deleted thing is in what it was; and a database reported drift forever on a restore-window timestamp AWS advances by itself every five minutes and that no API accepts.
  • Screens fit the terminal you are actually using. The release list — the one you read while choosing which version to go back to — was drawn at a fixed 103 columns, identical whether your terminal was 56 columns or 200. Inside Bronco’s own console that pane is 56. It now has two shapes: six aligned columns when they fit, and the version and what is live on one line with the receipt beneath when they do not. The version is never shortened, because bronco rollback --to takes it exactly as printed. The other overflows all came through one door: at 80 columns the scan screens had no over-long lines at all, and every one that existed came from the two logging calls that had never learned to wrap — which is where the honesty sentences come out, the ones that say what a narrowed screen did not count.
  • Six places bronco doctor argued with itself. START HERE omitted a High while showing a Low, under a heading reading worst first; urgency now decides who is on the list and variety fills what urgency leaves, with the per-family headlines still on the panel above. Two totals thirty lines apart could not be reconciled — both were right, over different sets, and only one said which. Bronco’s own named two different things (whose decision a finding was, and why it is not scored); the second now names the actual reasons. The In AWS lookup keys sat in a block of their own after fifteen rows, two of them sharing a prefix; each key names its row now. You are paying under $1 a month sat above a section totalling $98.55/mo — different quantities, and nothing said so. And the NAT gateway remedy counted the whole region, so two environments with one gateway each were told in dev or staging, one is enough, advice already followed.
  • The unlicensed screens say what they sell. The line was 99 columns long, carried no price, did not say what you get, and sold fixes two lines under bronco fix --dry-run labelled free. Three sentences now: the diagnosis is free on any account and always will be; Developer, $29/mo, is what acts on it — apply the fixes, build staging and production, watch the account continuously; and the link.
  • Findings name your load balancer, not its serial number. Several of the things Bronco builds get their real AWS name from a timestamp — your load balancer is called something like abtest2026082902421978080000001d, your database abtest-qa-main-2026082902420928170000001b. That was the name the free screens asked you to go and act on, and it tells you neither which project nor which environment it belongs to. They now read abtest-qa-alb and abtest-qa-main-postgres, taken from the resource’s own tags. The identifier AWS answers to is still on screen, because the AWS console lists a load balancer by its name and a database by its identifier — a screen that showed only the friendly name would have sent you somewhere you could not act. The check that tells you a service is down right now was the worst case: its target group is called tg- followed by twenty-six digits and nothing else. It names the service.
  • Bronco stops grading a qa environment as if it were production. If you name an environment anything other than dev, staging or prod, Bronco asks which of those its durability defaults should come from. It now records that answer on the environment’s resources, so the free scans can see it. Before, they could not: an environment called qa built on the dev preset had every one of Bronco’s own dev defaults — one zone, one task, one day of backups — counted against it as a production failure. On a real one that was five findings, and a reliability grade of 50/100 where the same stack named dev scores 77. Applying this to an environment you already have is a tag change and nothing else: measured on a live stack, 53 resources updated in place, none replaced, no downtime and no redeploy. An environment named dev, staging or prod is untouched — the name already answers the question. One built before this existed keeps being scored the conservative way until you re-apply it, and the screen says so.
  • A list of three problems reads like three problems. A group of findings shows one explanation and one fix above the list. That is right when the group is one kind of problem across several resources, and wrong when it is one resource with several problems — which is what “RDS resilience” was: three different things about one database under one heading, with a sentence describing one of them and a fix for one of them. The other two had no words of their own anywhere on the screen.
  • Error reports no longer hand out build advice during a teardown. A free-tier restriction hit while destroying an environment was answered with "lower the service's criticality so a smaller class is derived" — a redesign, printed to somebody deleting the thing, reading as the only way out and not being a way out at all. The fact stays on every screen; the remedy now depends on whether the operation is building or tearing down, and a teardown is told that nothing needs resizing to be deleted.

Fixed

  • Alarm advice that fits the alarm. A composite alarm has no metric and no dimensions, and bronco risk was telling you to point one at a dimension it cannot have. It now says what a composite alarm actually needs — the alarms its rule names. And the “add a metric-math FILL” advice is no longer offered on alarms that already have one, which includes every alarm Bronco builds.
  • bronco sweep --all-regions says when it covered fewer regions than you asked for. If AWS cannot list your regions the sweep falls back to one, and now says so, the same way the scans do. A clean one-region answer used to read as a clean account.
  • A compliance control that Bronco can only partly see is no longer reported as passed. The MFA check now covers console sign-in specifically, which is correct — and three controls it feeds (HIPAA 164.312(d), ISO A.8.5, PCI 8.4) ask about authentication for all access. An account whose only users are programmatic identities with long-lived keys would have flipped those from failing to passing in an auditor’s evidence pack. They read NOT ASSESSED now, and the pack states which half was examined.
  • An internal file path from Bronco’s own source was published on the docs site. It is gone, and the check that was supposed to catch it is part of the test suite now instead of a step in a document — which is why it survived months.
  • bronco cost could report a network as paying to reach S3 when it already had a working S3 endpoint. The check compared the endpoint’s status against the value AWS documents rather than the one AWS returns, so every healthy endpoint read as absent. Found by running the screen against a real account; the test suite and two independent reviews all passed over it.
  • Two cost checks made an AWS call in regions with nothing to check. They read the network layout before asking whether the region has a NAT gateway at all — the guard the neighbouring check has always had. On a scan across every region that is dozens of avoidable calls.
  • bronco watch would have reported the renaming above as a fresh security regression, once, in your Slack — unattended, on a schedule, with nothing in AWS having changed. It keys findings on what they are, not on what they are called.
  • The line reading 12 checks ran · 6 found something was counting something else. The two numbers agreed for exactly as long as one check produced one group.
  • bronco doctor could promise the grade for fixing one finding while naming a different one on the line above.
  • A teardown will not tell you something survived unless it checked. A real teardown ended by saying one resource had survived and was still running, named a database snapshot, and told you to go delete it in the AWS console. The snapshot was not there — bronco sweep, half a minute later, found nothing at all. Two kinds of resource had no liveness check at all, so Bronco assumed they were alive rather than asking; both are checked now, along with a third nobody had listed. The bigger fix is the sentence: “this is still running” and “I could not check this” used to print identically, and they are now different lines with different next steps — something Bronco confirmed is running points you at the console, something it could not check points you at bronco sweep, because re-reading is what settles it. AWS’s own resource index lags behind a teardown by design, and a teardown runs at exactly the moment it is most stale. Nothing was dropped from the alarm to make this work: a resource Bronco cannot check still counts against the teardown, it just no longer gets described as a fact. bronco sweep and bronco forget say it the same way, so no two screens can disagree about the same account.
  • Plans stop showing a change that was never real. Every stack with a database carried one permanent pending change, caused by a database setting being requested in a way AWS quietly stores differently rather than rejecting. It was harmless, and it was noise — and noise is what makes a real change hard to spot. A plan with nothing to do now says so.
  • A dry run can no longer strand an environment you already built. Running bronco forge --dry-run in a project with a live environment replaced that environment's state backend with a placeholder. From that moment every command that operates it refused — plan, apply, outputs, deploy, rollback, pause, restore, and destroy — and none of the escapes worked without a terminal: the refusal pointed at bronco forge --config, which will not take over a live stack, and that pointed at bronco discover, which requires one. A pipeline previewing a change to its own production environment lost every headless way to operate it, including the one that stops the bill. Forge builds an environment; to preview a change to one that already exists, use bronco plan — which is what Bronco now says instead of doing the damage.
  • The headless build screen shows one cost, not two. It printed a running estimate that had not yet taken your resilience answer into account, so the same environment appeared as ~$219/mo two lines above ~$98/mo, and the first was the one you read. The interactive build never had this, because it asks what ten minutes of downtime costs before it reads your contract; the headless path did it in the other order. It now asks in the same order, and the running total stays quiet where there is no next question to answer.
  • A finding now says what actually happens if you ignore it. “This is how accounts get taken over” was printed under every high-severity finding in the product — true of a public S3 bucket, false of CloudWatch alarms publishing to a topic nobody is subscribed to, which is one of the places it appeared. Exposure and reliability now get sentences that are true of their own kind. The fix offered for unsubscribed alarms also stopped pointing at bronco forge, which creates environments and has nothing to say to one that already exists; it now carries a command you can run against the environment you have.
  • bronco sweep no longer reports that nothing is running while something is. It answers two independent questions — does your registry account for this resource, and is it still alive — and the first was quietly gating the second. Every resource belonging to a registered environment was reported as accounted for without ever being checked, and “registered” meant any environment not explicitly torn down, including one whose build FAILED or was interrupted part-way. Measured on a real account: 21 resources across three environments that never finished, 11 of them still running, under a screen reading nothing Bronco owns is both unaccounted for and still running. Among them a CloudFront distribution, live, whose id appeared nowhere in bronco sweep --json. An unfinished environment is now its own verdict, named on screen with the stamp its last operation left, every resource is checked, and --json carries them all.
  • bronco destroy no longer says the charges stop over something it left running. The teardown already asked AWS what still carries the environment's tags and already sorted the answer four ways — and read one of the four. Anything still alive was folded into the same line as a tag AWS had not dropped yet, described as “often just deletion lag; re-check in a few minutes”, under a green tick saying charges stop now. The four kinds of leftover are now four sentences, each covering what the others do not: what survived and is still running (named, one per line), what sits in a deletion window, what outlived the teardown and costs nothing to keep (3 IAM roles, by kind — because a leftover role is a permission that outlived the thing it was for), and what really is deletion lag.
  • What Bronco charges you for is now checked type by type, not by whichever types someone had gotten to. Of the 46 kinds of AWS resource sweep can name, 14 were checked against AWS and 32 fell through a rule that assumes anything unchecked is still costing money — right in principle, silent in practice, and indistinguishable from a decision nobody made. Every kind now has an answer, and the ones deliberately left to that rule are named with the reason. Along the way: a deleted CloudFront distribution, a deleted WAF firewall and a deleted S3 bucket were all reported as still running, because the code recognised one of the three ways AWS says “this no longer exists”. Verified against a real account: all 23 checked kinds now report a deleted resource as gone.
  • A production environment built from your own bronco.app.yaml is now asked who receives its alarms. The question lives in the step that decides what an environment has, and a contract that declares its own resources: block answers that step from the file — so the question was skipped, and the environment was built with the full set of CloudWatch alarms publishing to a topic nobody is subscribed to. It reads as monitored on every screen that lists alarms, and the first person to learn the database filled up is a user. Production now refuses a blank address on this path as it already did on every other; below production it is asked and may be left empty. A contract that names an address is not asked again.
  • The load balancer’s firewall stopped describing doors that do not exist. Its security group opened port 443 on every environment, and only a custom domain puts a listener there — measured on a real stack: two rules, 80 and 443, and exactly one listener. Nothing was reachable through the extra rule, because a load balancer drops traffic on a port nothing is listening on. The cost was that your inventory, and every audit reading it, said this load balancer serves HTTPS when it did not. The rule now exists only where the listener does. A live environment renames the rule rather than rebuilding it, so nothing goes down.
  • Your application’s firewall no longer accepts every port from the load balancer. The rule allowed 0-65535, defended by the worry that a fixed port breaks when a container listens somewhere else. It does not: the load balancer reaches your task through a target group set to the same port, so a container listening elsewhere already fails its health check today. The rule is now the range your services actually use.
  • An environment with no public service was given a load balancer firewall anyway — a security group open to the internet on port 80, attached to nothing, on a stack that has no load balancer at all. Found by planning that case rather than reading it. Such an environment now provisions 26 resources instead of 33.
  • A production environment whose name is not exactly “prod” was quietly graded as if it were a dev stack. Bronco reads posture from a resource’s tags, and the tag carries the environment’s NAME — so prod-eu looked like “not prod”, and every one of its reliability findings was downgraded to informational and dropped out of the grade. That is reachable on purpose now that you can choose a cheaper resilience level for production: such an environment really does run a single-AZ database, and that was exactly the finding being silenced. Only the built-in dev and staging names are excused now. A name of your own is graded as production, and the screen says so rather than leaving you to assume it was checked.
  • bronco restore judged your backup posture by the environment’s name. A qa environment based on dev was warned about having no recovery points as though it were production, and a production environment with a name of its own got the reassuring sentence. It now reads the posture the environment was actually built with.
  • The database log group AWS creates on Bronco’s behalf can now be bounded. Turning on database log export makes AWS create a log group with no retention, which grows for as long as your database runs — and Bronco reported it back to you as your problem to fix by hand. bronco fix can now set a retention window on any log group that has none, and says which groups exist because Bronco turned an export on. It always asks first, even under a policy that authorises unattended fixes: applying a window deletes anything already older than it, and undoing the fix does not bring those lines back.
  • The release receipt printed $0/mo → $0/mo for an environment costing $89 a month. Bronco asked AWS for the last 30 days of billing; AWS reports what was billed and runs a day or two behind, so an environment created this morning answers zero — and zero was treated as a measurement. A stack that exists cannot cost nothing. The receipt now falls back to the estimate and labels which number it is showing, and an unknown change prints a dash instead of a confident +$0/mo.
  • A custom health-check path survives being written to bronco.app.yaml and read back. It was never written, so a later bronco forge reading your own contract fell back to the default path — and the deploy’s health gate then waited on a URL your app answers 404 on, for five minutes, before failing a deployment that was fine. Both spellings are accepted: health, and health_check_path.
  • The “what should this environment have?” question no longer arrives with a cache pre-ticked. The note above it says nothing is provisioned just in case, and a pre-ticked cache is a real monthly bill for anyone who reads that and presses enter.
  • bronco sweep said resources had “outlived the teardown” when it had already measured them as gone. AWS keeps answering tag queries with a resource for a while after it is deleted, which is why Bronco probes each one before saying anything. Six resources across three destroyed environments were probed, found gone, and then listed four lines under a heading that correctly said there was nothing left to remove. The sentence now matches the section it is printed in.
  • The load balancer now probes a readiness path, and the scaffolded starters answer it. Every starter answered /health with a static "ok" that touched nothing, and /health is the path the load balancer probed. After an RDS Multi-AZ failover, one task held ten dead database connections and served 100% errors for 14m45s while the load balancer reported every target healthy — so it was never taken out of rotation and never replaced, and the outage ended only when a human forced a redeploy. Starters now ship /health for liveness and /ready for readiness, with the place to register one check per dependency, a two-second deadline, and a 503 naming what failed. bronco forge and bronco evolve switch the probed path only when the app already answers /ready, or when Bronco is the one scaffolding it; anything else keeps the path it declared.
  • The load balancer health check no longer passes on a redirect to nowhere. It accepted any status from 200 to 399, which made it a gate that cannot see the failure it exists to catch: an environment with no custom domain answers on port 80 with a redirect to a port nothing is listening on, and that redirect counted as healthy — so a deploy that served nothing reported green. Redirects are now followed, up to five, and what has to answer 200 is where they land. An app whose root redirects to a login page is still healthy; a redirect to nowhere fails at the connection, and a loop fails on the bound. A 304, which carries no destination at all, stops counting as healthy.
  • The advice for a threat-detection refusal sent you to a console that could not help. When AWS reports the account is not signed up for Amazon GuardDuty, Bronco used to call the console sign-up the one step the API cannot perform, and offer it as the only way out. Two real accounts disproved it without anyone opening a browser: an account on the AWS Free Tier plan refuses every GuardDuty call and started answering about forty seconds after its plan changed, and an account forty seconds old refused and accepted a minute later. The message now names the account plan first — the one cause a console visit cannot fix — then says to retry, and keeps the console as the fallback.
  • The 5xx alarms can return to OK, so a second incident still notifies. One alarm entered ALARM during a load test and was still in ALARM hours later — through a second incident that took the same stack to 100% failure, which therefore notified nobody, because CloudWatch fires on state changes and this alarm had no state left to transition to. The load balancer publishes its 5xx count only in periods where a 5xx happened, so both alarms now fill every quiet period with a real zero and come back down on the period after a burst.
  • The Redis CPU alarm could never fire: it was pointed at the replication group, and ElastiCache publishes those metrics per node. On a live production stack it reported insufficient data from the moment it was created through thirty minutes of real load, while the node itself was at 5% engine CPU — and it is the cache's only alarm, so a saturated cache and an idle one looked the same. The three Redis widgets on the dashboard drew an empty chart for the life of every stack for the same reason. Both now read the node, so the alarm can fire and the charts carry data.
  • The saturation alarms — service CPU and memory, request p99 latency, database CPU and cache CPU — now sample every 60 seconds and fire after five consecutive samples above threshold, instead of averaging over five minutes and requiring two such windows. A service pegged at 100% CPU for eight consecutive minutes moved none of the nine alarms under the old shape, at any point in the sampling cycle, because averaging over a five-minute bucket dilutes the spike the alarm exists to catch. The floor is now five consecutive minutes above threshold, and a four-minute spike still does not fire. The alarm window is shorter than the measured 5 min 47 s the autoscaler takes to relieve a breach, so it fires when scaling has not relieved it or cannot. CloudWatch bills the same flat rate per alarm at this resolution, so the change costs $0. The 5xx and free-storage alarms keep their existing windows, which averaging does not distort.
  • Resuming an interrupted bronco apply is no longer refused for the Elastic IPs the environment already holds. A production environment whose first apply died partway held 4 of its region's 5 Elastic IPs — 2 on its NAT gateways, 2 on its load balancer — and the resume was refused with "needs 2, 0 free of 5" against a plan whose 17 resources created no gateway, offering as the only remedy the release of an Elastic IP the environment itself owned. The Elastic IP quota check now counts only what the apply would add. A new environment at the region's Elastic IP limit is still refused.
  • Destroying a protected production stack no longer re-declares the whole environment first. Lifting deletion protection ran a full apply of the environment's configuration, so any drift the account had picked up since blocked the teardown — a free-tier restriction on an instance class the account no longer accepts stopped a destroy dead, on a value nobody was trying to change. Worse than the drift is the direction: an apply creates what is missing, so a teardown could be asked to build a database in order to delete one. Deletion protection now comes off RDS and the load balancer through one narrow AWS call each, matched by tag so a neighbouring environment in the same account is never touched. The few settings that can only be changed through the engine are planned without re-reading the account, and applied ONLY if that plan is pure in-place updates; a create, a replace or a delete is refused, and the teardown carries on with what has already been lifted.
  • The teardown screen names the one thing its leftover check actually reads. It announced that it was checking for resources no state file tracks and, on a clean pass, that there were no untracked leftovers — while reading retired databases and nothing else. A teardown that had just printed that then wedged for forty-five minutes on a security group created outside Bronco. Anything inside the network jams a network teardown, tagged or not, so the line now says it looks for databases a restore left behind.
  • bronco destroy no longer claims the bill ends while resources sit in a deletion window. It announced that AWS charges for the environment stop now over a customer-managed KMS key scheduled for deletion seven days out and five Secrets Manager secrets inside their recovery window — all of which AWS keeps billing until the window closes. bronco sweep, run on the same account minutes later, already reported exactly those as winding down. The summary now says so and points at bronco sweep for the list; the resources go away on their own when the window closes.
  • bronco simulate --size now refuses with exit code 2 on an environment whose capacity comes from per-service criticality, and names bronco evolve --service <name> as the lever that generation has. It used to accept the flag, change nothing, and report the result: on the same stack the older global sizing moves the estimate from $273/mo to $515/mo, while the newer model moved $232/mo to $232/mo. bronco simulate --add is unaffected. The error report shown when an account caps an instance size now names both levers — lowering a service's criticality, or the global size for environments that still use it — instead of only the one that a newer environment does not have.
  • bronco sweep now names four more resource types instead of printing a raw identifier fragment: the firewall (WAF), the alert topic, the VPC flow log and the encryption key alias. SNS topics and KMS aliases are reported as free to keep rather than as possibly billing, which is what keeps a sweep's exit code 2 meaning "something is unaccounted for and still running". The web ACL is now queried against AWS for real: it costs about $5/month per ACL, and it previously reported as active whether or not it still existed.

Security

  • bronco audit now finds every administrator, not just the obvious one. The check that reports a credential able to take over your whole account recognised two ways of holding that power. There are five, and the three it missed are how an account with more than one administrator is normally set up: a policy of your own that grants everything, membership of a group carrying AdministratorAccess, and a group whose own policy grants everything. Measured on a real account — three users holding one of those routes each, and the audit reported none of them: the same clean answer it gives when nobody has that power. All three are now reported, each naming the route it came by, and an account with no administrators reads exactly as it did before, in the same time. Policies are read rather than recognised by name, so a permission Bronco cannot read produces a stated coverage gap instead of a clean bill.
  • An auditor report could pass “access control” with your account in one pair of hands. SOC 2 CC6.1, HIPAA 164.312(a)(1) and ISO A.5.15 all read PASS on an account whose only problem was a user who could do anything, because the check that answers that question backed no control. It now backs all three.
  • bronco audit and bronco doctor now flag a production service that still allows an interactive shell into a running task through ECS Exec. Anyone who can open that shell reaches the environment's database connection string and application secrets through the task's own role, without needing a flaw in the app. Environments built before the fix that closed this kept the shell open — including any named production or live, the names Bronco itself suggests when it refuses prod — and nothing looked for them. An environment's intended ceiling is not readable from AWS, so the finding fires on either of two facts that are: a production environment name, or deletion protection on a resource in the same environment. The evidence line names which of the two fired. The remedy is bronco apply for that environment, which closes the shell as an in-place update; bronco evolve over the same environment is not refused either, and no rebuild is required.
  • An environment built at the production posture but named anything other than prod had its application buckets and its Amazon ECR repositories left force-deletable, so a rename or replacement of either would wipe data and images without the friction production posture promises. That protection now follows the environment's posture, NOT its name. bronco destroy still removes both after its typed confirmation, so a deliberate teardown is unchanged.
  • bronco replicate from a dev environment to a production one built a public stack with no AWS WAF and no load-balancer access logs, because the source environment's answer was carried across as a decision. Edge protection is now derived at the target environment's posture, so a production target ships the firewall and the request log whatever it was replicated from. The same replication that planned 100 resources with no firewall now plans 110 with the firewall present and the buckets protected. Redis authentication is deliberately not derived, because it depends on the application sending the token.
  • The rate limit counts the visitor instead of the CloudFront edge in front of them. A measured flood of 15,440 requests from one address in 90 seconds — five times the limit — produced zero blocks, because the address being counted was a CloudFront edge and every viewer behind that location shared one counter. Had it ever tripped, it would have blocked the whole edge location, cutting off legitimate users while the abuser moved to the next one. A stack behind CloudFront now keys the limit on the forwarded client address; a stack fronted by the load balancer directly keeps keying it on the connecting address, where a forwarded header is written by the caller and would remove the limit entirely. One residual stays open: a client that sends its own forwarded header through CloudFront can still rotate its own counter, and closing that needs a rate limit at the CloudFront layer, which this stack does not build.
  • bronco restore brought the database back on the default parameter group, so a recovered production database no longer enforced TLS and had lost its slow-query log; the restored copy now inherits the source database's parameter group and storage settings. Deletion protection is turned back on after the cutover when the environment's posture calls for it — the copy is created deletable on purpose, so that an aborted restore can be removed — and the screen states whether that succeeded.
  • Alert notifications now ride an encrypted Amazon SNS topic, using the same key as the rest of the stack — what travels on it is the shape of an incident, and it was the last channel in the clear. The key policy grants CloudWatch permission to publish to it, scoped to the account; without that grant every alarm would have failed to publish in silence.
v0.15.2 July 2026

Added

  • Signed license entitlements — live. Licensing is now tamper-proof and needs no runtime backend: entitlements are cryptographically signed and the CLI verifies them offline. An unsigned, forged, or expired license is rejected, and offline grace is bounded by the signed expiry. Validating a license sends no account data or prompts off your machine.
  • IAM-enforced RBAC. bronco roles provision <user> <role> creates the role's managed policy, the teammate's IAM user, attaches the policy, and mints access keys — so AWS itself enforces the tier instead of the CLI merely advising it.
  • Commercial plan model — Free + Developer + Agency + Enterprise. Developer at $29/mo · $290/yr; Agency at $149/mo · $1,490/yr; Enterprise custom, on top of a permanent Free tier. bronco license shows your active plan and its price. The landing page carries a four-plan grid, monthly/annual toggle, capability table and refreshed FAQ.
  • Optional plugin integrity pinning. An entry in bronco.plugins.json may declare an integrity checksum that is verified before the plugin loads; unverifiable external sources are refused loudly. Backward-compatible with existing plugin lists.

Changed

  • Consistent per-tier enforcement. Tier requirements are now applied uniformly across every gated command — closing earlier gaps where some team, billing, compliance and governance commands ran without a license check. Read-only, recovery and teardown commands remain free, and conversational chat stays free to converse (its write actions self-gate).
  • Three-pillar CLI entry. Running bronco with no arguments now opens 🔨 Forge · 🩺 Doctor · 🛠 Operate — plus a first-class ✨ Ask AI door — replacing the two-door entry. A consistent "Next" step after forge and deploy keeps the build → operate → diagnose flow discoverable.
  • The infrastructure report now reads in English and names the edge-TLS posture with a custom-domain recommendation when applicable.
  • Internal architecture cleanup for maintainability and testability, with no change to command behavior.

Fixed

  • Corrected request signing for AI inference through Amazon Bedrock so requests that carry query parameters are signed correctly (previously only the parameter-free path signed correctly).
  • Cost-accuracy reporting no longer penalizes a stack that has no billing data yet — such stacks are reported as no-data and kept out of the accuracy score.
  • bronco billing --by project|environment now warns clearly when cost-allocation tags are not yet active (every group returning (untagged)) instead of a silent lone (untagged) 100% row.
v0.15.1 June 2026

Security

  • Scoped the bronco-deployer IAM policy to the minimum required permissions.
  • Terraform state is now encrypted with a customer-managed KMS key.
  • External plugins are gated behind explicit operator consent before import.
v0.15.0 June 2026

Added — Operate, the third pillar (govern code↔cloud)

  • Value-gated releases. bronco deploy now snapshots cost/security/reliability before, deploys, re-diagnoses after, and applies a verdict from bronco.operate.json. A regression beyond policy in an enforce env auto-rolls-back to the previous task definition — reversible, logged, with a release receipt. Advisory by default (every env is warn with no policy file); --no-gate skips it.
  • bronco operate — the front door. connect wires a repo to a stack (auto-detection + three questions, Forge fall-through when there's no infra), installing a GitHub Actions + OIDC pipeline as a file you commit — installed, not hosted, no Bronco backend. status is the cockpit: every repo→service→env→account on one screen.
  • Code↔infra co-evolution. bronco operate sync detects when the code outgrew the infra (a new REDIS_URL, queue or database) and proposes the Forge change with the exact monthly delta.
  • Versioned, promotable releases. A Bronco version is an atomic bundle of code SHA · image digests · infra config · receipt. bronco operate promote <ver> --to <env> re-runs the gate in another env; bronco operate releases lists versions with impact + changelog; bronco rollback --to <ver> reverts to a named point.
  • Human-only overrides. bronco operate approve --release <id>-bypass (JIT escalation, one release) and bronco operate override --release <ver> --reason "…" (re-promote after a false-positive rollback). The AI proposes; the write always stops at a human.
  • Pipeline topology. One workflow per repo, one deploy per changed service — a monorepo matrixes bronco operate changed --json over the dirty services.

Added — the way in (Doctor, MCP, two-door entry)

  • bronco doctor — the way in. One read-only pass over any AWS account folds cost, security and reliability into a single value receipt — what you'd save, what's exposed, what's fragile — and ends with the one next step that matters. Always free: it never touches the license and never changes a thing.
  • Two-door entry. Running bronco with no arguments now opens two doors — Doctor to see your account in 60 seconds, or Forge to build and ship. The help is reorganized into four loops: Diagnose, Operate, Protect, Prove.
  • bronco mcp — Bronco as a tool your AI agent can call. AI writes the app fast; shipping it to production-grade AWS safely is the hard part, so bronco mcp exposes Bronco over MCP as that deploy/ops primitive. Read-only in v1: the agent looks and proposes, every write still stops at a human.

Changed

  • bronco init is now bronco forge — the same conversation, a name that fits the third pillar. The init alias keeps working, so existing scripts and CI don't break.
v0.14.0 June 2026

Added — conversational operator, compliance evidence & plugins

  • Conversational operator. bronco ask answers read-only questions about your account in plain English; bronco chat goes further and proposes changes that run through the existing policy + approval gates. Inference uses your own model access — your Anthropic key or Claude on Bedrock in your account — so there is no Bronco inference server and your prompts never leave your machine.
  • Audit evidence packs. bronco audit --compliance adds ISO 27001 and PCI-DSS baselines alongside SOC 2 and HIPAA, and --evidence exports a dated, branded pack — each control's status with the live finding that proves it.
  • Org-wide governance. bronco.org.json declares guardrails + an SSO→role map across every account; bronco org check exits non-zero on a breach, so CI enforces it.
  • Plugin SDK. A stable, semver'd @bronco/plugin-sdk for custom checks, notifiers and compliance maps — resolved from git, OCI or your own S3, read-only by contract (external plugins require explicit consent before they load).

Changed

  • AWS remains Bronco's single, first-class stack. The exploratory multi-cloud work was set aside to keep the AWS experience deep and fluid.
v0.13.0 June 2026

Added

  • Multi-channel notifications. bronco watch now delivers to Slack, Teams, Discord, and any generic webhook — and each channel accepts several URLs, so one stack can notify multiple workspaces. The generic webhook receives the raw digest JSON.
  • Autonomous remediation on a schedule (opt-in). The watch engine can now apply the same policy-approved fixes as bronco fix --auto, closing a regression within hours instead of waiting for someone at a terminal. Off by default: it needs a bronco.policy.json with auto rules, and the Lambda gains write access only to the two narrow, reversible fixes — never delete, never IAM. Every action is recorded and undoable; each digest reports what it fixed.
  • IAM-enforced RBAC. bronco roles policy <role> emits a least-privilege IAM policy you attach to a teammate's IAM identity, so AWS itself enforces the tier — viewer is provably read-only, developer gets service-scoped management with a hard Deny on identity governance, admin is unrestricted.

Fixed

  • Summary boxes that silently failed to render (the cost / audit / risk headers, simulate, timeline, the remediation plan, and watch status) now print correctly.
v0.12.0 June 2026

Added — autonomous fixes, cost simulation & change history

  • bronco fix — autonomous, policy-gated, reversible remediation. Turns audit findings into an ordered plan, then applies what a bronco.policy.json pre-authorizes (auto) or you approve live (approve). --auto runs it from CI/cron; --dry-run previews free; --undo <id> rolls any action back. Destructive changes are never automatic; the audit log lives in your own state bucket.
  • bronco simulate (and cost --simulate) — forecast the monthly spend delta of a change before apply (resize, add a service, enable a DB/WAF, promote to prod), grounded against real Cost Explorer spend. bronco plan now shows the estimate + delta-vs-last-apply too.
  • bronco timeline — the infrastructure intelligence graph: an append-only history of every apply / deploy / drift / fix with cost delta and reason, queryable by --kind and --since. Records app-change → infra-delta topology.
  • The watch digest now reports how many findings Bronco could auto-fix — the engine stays read-only and only nudges bronco fix --auto.

Safety model

  • Every autonomous write is policy-gated, allowlist-fenced, and reversible; no Bronco-hosted backend — policy, history and audit logs all live in your own S3. Certified against real AWS (the full detect → fix → undo loop) before release.
v0.11.0 June 2026

Added — the agency layer (multi-client)

  • bronco billing — cost allocation from Cost Explorer grouped by project, environment or client tenant, rendered to branded xlsx/pdf. --by tenant rolls spend up per client across all of their accounts.
  • bronco audit --compliance soc2|hipaa — crosswalks live audit findings to SOC2/HIPAA controls (pass / fail / not-assessed) with a posture summary. Preview free; full posture + branded report with a subscription.
  • White-label reports. bronco report, billing and compliance now render to .pdf as well as .xlsx, themed by a bronco.brand.json (name, accent, contact, footer). Reports fold in an estimate-vs-actual cost reconciliation line.
  • bronco tenants & bronco roles — group connected accounts by client tenant, and scope teammates to admin / developer / viewer (advisory RBAC; defaults to admin, so existing setups stay unrestricted).
  • The whole agency surface is read-only and account-agnostic — only Cost Explorer is billable — and certified against real AWS before each release.

Changed

  • bronco report is now branded and multi-format (--format xlsx|pdf, --brand). With no flags it still produces the same Excel report as before.
v0.10.0 June 2026

Added — continuous monitoring & drift detection

  • bronco watch — a scheduled, read-only monitoring engine provisioned in your own account (EventBridge + Lambda). It runs the cost/audit/risk scans on a cadence and pushes a digest to Slack or Teams, with proactive alerts on four signals: cost spikes, security regressions, drift, and reliability regressions. Each digest reconciles estimated vs actual Cost Explorer spend. on / off / status / digest / test.
  • bronco drift — declared-vs-live detection from a read-only Terraform refresh, baseline-diffed so a freshly-applied stack reads clean and only genuine out-of-band changes surface. --reset re-baselines; exits non-zero on new drift so CI can gate.
  • The watch engine runs entirely in your account — no Bronco-hosted backend, no cross-account role. Setup is license-gated; tearing it down or reading status never is.
v0.9.0 June 2026

Added — cloud cost, risk & security on any account

  • bronco cost — real AWS spend from Cost Explorer + waste detection (unattached EBS, idle Elastic IPs, gp2→gp3, stale snapshots), with a potential-savings figure.
  • bronco audit — security-posture scan scored 0–100 (public S3, security groups open to the world, root/user MFA, RDS exposure & encryption, unencrypted EBS), plus interactive --fix micro-remediations.
  • bronco risk — production-readiness scan (single-AZ databases, missing backups, no deletion protection).
  • All three are agnostic and read-only — they work on any AWS account, Bronco-built or not — and run free in preview; the full itemized report and the fixes unlock with a license. --all-regions sweeps every enabled region.
v0.8.0 June 2026

Added

  • Hardened one-click AWS bootstrap. The quick-create CloudFormation now attaches a least-privilege permissions boundary to the bronco-deployer identity: destructive actions are fenced to Bronco-tagged resources only — no reaching a foreign database, secret, cache, or KMS key — IAM self-escalation is denied, and the "create a role, then run as it" laundering path is closed.
  • Real-AWS release certification. Every release is now gated by a suite that provisions, deploys, rolls back, and tears down real stacks of several shapes — proving each returns to $0 — alongside deployer security probes, before any binary ships.

Fixed

  • Deploy via AWS CodeBuild pushed an unusable image tag. When building in the cloud (no local Docker), the image went up under an empty tag while the ECS task definition pinned the real immutable one — so the service failed with CannotPullContainerError: … not found. Build, push, and task definition now share one immutable tag end to end.
  • Web services default to port 8080. The old default (80) needs root inside the container; 8080 works out of the box for non-root images — the safer, more common setup.

Removed

  • bronco-deployer-cfn.yaml — the legacy bootstrap template, replaced by the hardened bronco-deployer.yaml. If you bootstrapped an account with the old template, update the stack to pick up the new permissions boundary.
v0.7.1 June 2026

Fixed

  • Clearer deploy failures. bronco deploy surfaces the ECS task stop reason directly in the report instead of a generic timeout.
  • More reliable rollouts. Deploys poll the primary ECS deployment (bypassing stale failed ones), wait up to 15 minutes for stability, and copy task-definition tags correctly on each image update.
v0.7.0 June 2026

Added

  • bronco discover — reconnect to environments Bronco provisioned from any machine by scanning your account's state bucket; no local workspace required.
  • Custom environments. Go beyond dev / staging / prod with your own named environments.

Something missing? firesetphyro@gmail.com