Core Concepts
The CLI persists a small set of artifacts and orchestrates AWS calls around them. Once you understand the artifacts and how deploy weaves them together, every command is obvious.
The artifacts
project-root/
├── kanject-cli/
│ ├── manifest.json ← service identity + cross-repo deps + stages
│ ├── manifest.lock.json ← pinned commits + content hashes (sync output)
│ ├── stages/
│ │ ├── dev.json ← per-stage region/profile/stack/bucket/env
│ │ ├── stage.json
│ │ └── prod.json
│ └── (preview.json) ← optional: ephemeral-deployment policy
└── (s3://<artifactBucket>/_ledger/versions/*.json) ← deployment history The first five live in git so reviewers see every change. The deployment ledger lives in S3 so every deploy is auditable independently of the working copy. The per-stage aws-lambda-tools.<stage>.json is regenerated by deploy from inputs — never hand-edit it.
Service identity
manifest.json → service.name is the canonical identifier for everything downstream — CloudFormation stack ({stage}-{service}), ECR repository, artifact bucket ({stage}-{service}-artifacts), Parameter Store path (/{service}/{stage}/), and the deployment-ledger prefix all derive from it. The name is normalized: lowercase, hyphens, must start with a letter.
Stages
A stage is a deployment target — typically dev, stage, prod, but the CLI doesn't care: qa-eu, staging-row, canary all work. Every stage in manifest.json → aws.stages needs a matching kanject-cli/stages/<stage>.json pinning region, profile, stack, artifact bucket, parameter-store path, and the env map.
env values come in three flavours:
# Plain string — baked into Lambda env config at deploy
ASPNETCORE_ENVIRONMENT=Development
# Parameter Store reference — resolved at deploy time
DATABASE_URL=param:DATABASE_URL
# Secrets Manager reference — resolved at deploy time
JWT_SIGNING_KEY=secret:acme-analytics/dev/jwt#key References are resolved once at deploy then baked into the Lambda function config. The Lambda runtime never calls Parameter Store / Secrets Manager — it just reads the env var. Cold-starts stay fast.
lambda:GetFunctionConfiguration can read them. Treat the function config as production-secret material.Cross-repo dependencies
Most teams have shared class libraries living in sibling repos. The traditional <ProjectReference Include="..\..\sibling\Foo.csproj" /> works only on machines with the right relative paths — CI breaks first. Kanject's answer: declare cross-repo deps in manifest.json → dependencies[] with a git URL + ref + project path:
"dependencies": [
{
"name": "Acme.Identity.Data",
"repository": "git@github.com:acme/acme-platform.git",
"ref": "main",
"projectPath": "services/identity/src/Acme.Identity.Data/Acme.Identity.Data.csproj",
"consumers": ["src/Acme.Analytics.Api/Acme.Analytics.Api.csproj"]
}
] kanject sync clones each repo at ref into .kanject/cache/<repo>/<sha>/, runs dotnet pack, drops the .nupkg into a local feed, rewrites every consumer csproj from <ProjectReference> to <PackageReference>, and pins the resolved SHA + content hash in manifest.lock.json.
Deployment ledger
Every successful kanject deploy writes a JSON snapshot to s3://<artifactBucket>/_ledger/versions/<lambda-version>.json recording the published version ARN, timestamp + identity, commit SHA, lockfile hash, and per-env-value hashes. A pointer at _ledger/current.json tracks the latest entry.
The ledger is the source of truth for two things: audit ("what was deployed at 14:22 on Friday?") and rollback — kanject rollback reads the ledger, shows the last 10 deploys with status badges, and lets you flip the alias to any of them. It's an alias-flip, not a rebuild — the prior artifact is still immutable in S3 (zip) or ECR (image). The ledger is append-only; rollback writes a new entry.
What kanject does NOT own
dotnet new— kanject calls it, doesn't replace it. Templates are a separate NuGet pack.dotnet build/dotnet test— vanilla. The CLI doesn't proxy them.dotnet lambda deploy-serverless— the deploy backend. Kanject sets up inputs, then calls the AWS-supplied tool.- Your CloudFormation template — kanject orchestrates the deploy, but
serverless.templateis yours. - Your CI provider —
pipeline initscaffolds CodePipeline; if you want GitHub Actions or GitLab, you write the workflow. - Your code — kanject never edits anything in
src/.