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

bash
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:

bash
# 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.

Security note
Resolved values are baked into the Lambda function config — anyone with 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:

json
"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 rollbackkanject 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.template is yours.
  • Your CI provider — pipeline init scaffolds CodePipeline; if you want GitHub Actions or GitLab, you write the workflow.
  • Your code — kanject never edits anything in src/.