Bean & Bark: your first deploy
Bean & Bark's wholesale orders arrive by DM and spreadsheet, and the roastery's first tasting for retail buyers is Thursday. Nadia wants orders coming through an API before then. You have a fresh laptop, an AWS dev account, and one command surface. This is where you learn the thing the CLI is built around: a deploy isn't done when the code is up — it's done when the deployment ledger has recorded exactly what shipped.
- Scaffold a deploy-ready .NET service — solution, manifest, a
devstage, tests — with onekanject new - Prove your machine is ready with
kanject doctor, entirely offline - Deploy to the
devstage and follow the phase ladder torecord ledger - Predict every AWS resource name from two words: the service name and the stage
One command, a whole service
kanject new scaffolds from a template that ships inside the CLI — no feed, no license, nothing to authenticate. webapi gives you an ASP.NET Core REST API on AWS Lambda, split into Api, Business, Common and Data projects with an xUnit test project, and --yes accepts the prompts so it runs clean in one go.
# One command: a deploy-ready servicekanject new webapi --name BeanAndBark.Orders --yes# Every kanject command in this series runs from the API projectcd BeanAndBark.Orders/src/BeanAndBark.Orders.Api new runs dotnet new, then runs kanject init inside the API project, so the service is already wired — you never run init on a fresh scaffold:
BeanAndBark.Orders/├── BeanAndBark.Orders.slnx├── src/│ ├── BeanAndBark.Orders.Api/ ← the Lambda entry: run kanject here│ │ ├── template.json ← the SAM template deploy builds on│ │ └── kanject-cli/│ │ ├── manifest.json│ │ ├── manifest.lock.json│ │ └── stages/dev.json│ ├── BeanAndBark.Orders.Business/│ ├── BeanAndBark.Orders.Common/│ └── BeanAndBark.Orders.Data/└── tests/ └── BeanAndBark.Orders.Api.Tests/ kanject-cli/ holds the service's identity (manifest.json), its pinned dependencies (manifest.lock.json, empty for now), and one stage, dev. It lives in the API project, which is why the cd matters: run a kanject command anywhere else and it can't find the manifest (doctor reports KANCLI002). Every command in this series runs from src/BeanAndBark.Orders.Api.
Prove the machine is ready
Before anything touches AWS — a guess. doctor is about to run its checks: how many of them need AWS credentials? Run it and count.
kanject doctor ── Kanject Doctor ──────────────────────────────────────────────────────────────╭────┬───────────────────────────────────────────────────┬────────┬──────┬─────╮│ │ Check │ Status │ Code │ Fix │├────┼───────────────────────────────────────────────────┼────────┼──────┼─────┤│ ✓ │ dotnet 10.0.401 │ ok │ - │ - ││ ✓ │ global tools on PATH (/Users/you/.dotnet/tools) │ ok │ - │ - ││ ✓ │ kanject-cli/manifest.json (schema v2) │ ok │ - │ - ││ ✓ │ Amazon.Lambda.Tools 6.0.5 │ ok │ - │ - ││ ✓ │ kanject-cli/manifest.lock.json in sync (no deps) │ ok │ - │ - ││ ✓ │ service.entryProjects (1 entry/entries; api) │ ok │ - │ - │╰────┴───────────────────────────────────────────────────┴────────┴──────┴─────╯ok 6warn 0error 0── Next Steps ────────────────────────────────────────────────────────────────── kanject doctor --env <stage> Run stage-specific checks kanject doctor --features Review optional feature readiness The answer is none. Every row is local: the .NET SDK, the global-tools PATH, the manifest, the Lambda tooling, the lockfile, the entry project. Even kanject doctor --env dev stays local for a stage like this one; doctor only calls AWS to check a stage that declares a VPC (network) or a custom API domain. So it won't tell you your credentials have expired or your bucket is missing — the deploy will. The exit code is honest too: warnings keep it 0; only a red ✗ row makes it 1.
Point dev at your AWS
Open kanject-cli/stages/dev.json. A stage is the whole answer to "where does this deploy?" — region, profile, stack, artifact bucket, and the Parameter Store path for its config:
{ "$schema": "../stage.schema.json", "schemaVersion": 1, "region": "eu-west-1", "profile": "default", "stack": "dev-beanandbark-orders", "artifactBucket": "dev-beanandbark-orders-artifacts", "parameterStore": { "path": "/beanandbark-orders/dev/" }, "env": {}, "entryEnv": {}} Look at the names. You never chose them — they're derived: service beanandbark-orders + stage dev gives stack dev-beanandbark-orders, bucket dev-beanandbark-orders-artifacts, and the SSM path /beanandbark-orders/dev/. Two words predict every resource name, on every stage. Change the region or profile if yours differ.
One value before the first deploy. The scaffold's SAM template sets ASPNETCORE_ENVIRONMENT to Production on the function, so dev would load the production settings. Stage values win over the template's at deploy, so tell dev what it is:
kanject add env ASPNETCORE_ENVIRONMENT Development --env dev + set ASPNETCORE_ENVIRONMENT in kanject-cli/stages/dev.json aws s3 mb s3://dev-beanandbark-orders-artifacts --region eu-west-1 --profile default Ship it
kanject aws deploy --env dev ── Deploy stage dev ─────────────────────────────────────────────────────────── sync deps (no deps) 00:00:00 resolve env 1 keys 00:00:01 prepare artifacts done 00:00:00 write deploy config done 00:00:00 render provider artifacts done 00:00:01 deploy Lambda ok 00:01:42 promote revision 1 → live 00:01:45 record ledger s3 snapshot written 00:01:46╭────────────────────────┬────────────┬─────────────╮│ .NET key │ Source │ Value │├────────────────────────┼────────────┼─────────────┤│ ASPNETCORE_ENVIRONMENT │ StagePlain │ Development │╰────────────────────────┴────────────┴─────────────╯ ✓ Deployed dev-beanandbark-orders (Lambda) @ revision 1 resolve env found the one value you added, and the table shows what was baked into the function. Then read the last two phases, because they're the point. promote revision published Lambda version 1 — code and environment frozen together — and flipped the live alias to it; that number is about to matter. record ledger wrote an entry to _ledger/deployments/ in the artifact bucket and pointed _ledger/current.json at it: the revision, your git commit, who deployed, and a SHA-256 of the lockfile and of every resolved env value. And the ordering is a guarantee: if any phase fails, no ledger entry is written. The ledger never claims a deploy that didn't finish.
Notice what it didn't print: a URL. The template publishes the API's address as the stack output ApiURL, so that's what you send Tunde:
aws cloudformation describe-stacks --stack-name dev-beanandbark-orders \ --query "Stacks[0].Outputs[?OutputKey=='ApiURL'].OutputValue" --output text --region eu-west-1 --profile default Read it back
Don't take the terminal's word for it — ask the ledger:
kanject aws deployments list --env dev ── Deployments stage dev ────────────────────────────────────────────────────────────╭──────────┬────────┬──────────────────────────────┬─────────┬──────────┬────────────╮│ Revision │ Target │ Deployed │ Commit │ Status │ By │├──────────┼────────┼──────────────────────────────┼─────────┼──────────┼────────────┤│ 1 │ Lambda │ 2026-07-06T09:26:14.5821930Z │ 9f3c1ab │ ✓ stable │ you@laptop │╰──────────┴────────┴──────────────────────────────┴─────────┴──────────┴────────────╯ One revision, target Lambda, the UTC timestamp as recorded, your commit, status stable, and your user and machine name. Every deploy from now on — including Thursday's panicked ones — adds a row here. When something goes wrong, this table is where the recovery starts.
kanject new webapi --name <Name> --yesscaffolds a deploy-ready service with onedevstage and runsinitinside the API project — run everykanjectcommand from there.kanject doctorwithout--envis fully offline; only errors (not warnings) flip its exit code to 1.- Stage + service name derive every resource:
dev-beanandbark-orders,…-artifacts,/beanandbark-orders/dev/. The artifact bucket is the one thing you create yourself. - A deploy ends by publishing a Lambda version, flipping the
livealias, and writing a ledger entry — and a failed deploy writes no ledger entry.
deploy Lambda phase. What does the deployment ledger show afterwards?Show solution
aws-lambda-tools.dev.json and template.dev.json — the two per-stage files the real deploy uses — and ends "Stopped before dotnet lambda deploy-serverless." No build, no upload, no stack change, no alias flip, no ledger entry.kanject aws deploy --env dev --dry-run