# CI/CD Pipeline Setup

Ship on `git push`. One command — `kanject aws pipeline up` — provisions an AWS-native CodePipeline that builds, tests, and deploys every stage of your service in order, with a manual approval gate before production.

There are two ways to run Kanject deploys in CI. Pick one; both end in the same `kanject aws deploy`.

- **AWS-native pipeline** — CodePipeline + CodeBuild + CodeStar Connections, scaffolded and provisioned for you by kanject aws pipeline up. This page.
- **Bring your own runner** — Already on GitHub Actions, GitLab, or Buildkite? Skip the scaffolding and call kanject aws deploy from your runner.

## Before you start

- A Kanject service with `kanject-cli/manifest.json` and your stages declared in `manifest.aws.stages` — the pipeline emits **one deploy stage per entry, in order**.
- A **CodeStar Connection** to GitHub, created and authorized in the AWS console. The one-time OAuth handshake can’t be automated; once authorized, the ARN is stable.
- A **Secrets Manager secret** holding a GitHub PAT (used for private repo cloning inside CodeBuild).
- An **S3 artifact bucket** and your **CodeArtifact** domain / owner / repo coordinates.
- AWS credentials for the target account (`--profile` defaults to `default`).

## One command: pipeline up

`pipeline up` is idempotent and manifest-driven: it persists the pipeline config, scaffolds the `deploy/` files if they’re missing, and creates or updates the `<service>-pipeline` CloudFormation stack — all in one run.

```bash
# First run: supply the org-constant config once
# (persisted to kanject-cli/pipeline.json — later runs reuse it)
kanject aws pipeline up \
  --connection arn:aws:codeconnections:eu-west-1:111122223333:connection/abcd \
  --repo-owner kanject --repo-name kanject-analytics \
  --artifact-bucket kanject-pipeline-artifacts \
  --codeartifact-domain kanject --codeartifact-owner 111122223333 --codeartifact-repo internal \
  --github-pat-secret arn:aws:secretsmanager:eu-west-1:111122223333:secret:kanject/github-pat
```

Every flag is saved to `kanject-cli/pipeline.json`, so you pass it **once**. Both written files are reviewable JSON — commit them and the diff shows exactly what changed. Re-running with no flags is a clean no-op.

```bash
# Add a second deploy region to prod — config is already persisted
kanject aws pipeline up --stage prod --add-region us-east-1

# Reconcile after hand-editing deploy/pipeline.template — no flags needed
kanject aws pipeline up
```

## What gets provisioned

- A CloudFormation stack named `<service>-pipeline` containing the whole CI surface.
- A **CodePipeline** triggered on push to your branch: `Source → Build → Deploy<Stage1> → … → Deploy<FinalStage>`.
- A shared **Build** CodeBuild project (`kanject sync` + `dotnet build` + `dotnet test`) and a shared **Deploy** project (`kanject aws deploy --env $STAGE`) — the stage is injected per action, so there’s no per-stage buildspec.
- A **manual approval gate** before the final stage (skipped for single-stage services).
- IAM roles for CodeBuild plus the webhook that starts the pipeline on push.

The Build project runs `kanject sync` before `dotnet build`, so cross-repo dependencies resolve on the fresh runner — without it, `restore` can’t find a sibling repo’s project and the build fails. See [Dependency Sources & Resolution](https://www.kanject.com/docs/cli-dependencies/).

> **The template is yours:** Scaffolding writes `deploy/pipeline.template`, `buildspec.build.yml`, and `buildspec.deploy.yml` into your repo. Edit them freely — `pipeline up` never clobbers hand-edits; it only scaffolds when the template is missing.

## Prefer explicit steps?

The original two-step flow still works: `init` writes the `deploy/` files, `bootstrap` deploys the stack. `pipeline up` simply folds both into one idempotent command.

```bash
# 1. Scaffold deploy/ (pipeline.template + the two buildspecs)
kanject aws pipeline init

# 2. Deploy the pipeline stack (same flags as pipeline up)
kanject aws pipeline bootstrap \
  --connection <CODESTAR_CONNECTION_ARN> \
  --repo-owner kanject --repo-name kanject-analytics \
  --artifact-bucket kanject-pipeline-artifacts \
  --codeartifact-domain kanject --codeartifact-owner 111122223333 --codeartifact-repo internal \
  --github-pat-secret <SECRETS_MANAGER_ARN>
```

## Bring your own runner

On GitHub Actions, GitLab, Buildkite, or Jenkins the recipe is the same everywhere: install the .NET SDK, install `Amazon.Lambda.Tools` and `Kanject.Cli` as global tools, authenticate to AWS, then run `kanject aws deploy --env <stage> --non-interactive`. This disables prompts without silently granting approval.

```yaml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write   # AWS OIDC
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with: { dotnet-version: '10.0.x' }
      - run: dotnet tool install -g Amazon.Lambda.Tools
      - run: dotnet tool install -g Kanject.Cli
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-deploy
          aws-region: eu-west-1
      - run: kanject aws deploy --env stage --non-interactive
```

Gate production with your provider’s native approval mechanism — e.g. a GitHub *Environment* with required reviewers on the prod job.

## Multiple microservices

Pipelines are **one per service**, not one mega-pipeline per repo. Each service directory owns its manifest, its stack, and its `<service>-pipeline` — so a broken deploy in one service never blocks the others, and rollback stays per-stack.

```bash
contoso-wallet/                       # one git repo
├── wallet-api/
│   ├── kanject-cli/manifest.json     # service "wallet-api"
│   └── deploy/pipeline.template      # stack: wallet-api-pipeline
└── wallet-tasks/
    ├── kanject-cli/manifest.json     # service "wallet-tasks"
    └── deploy/pipeline.template      # stack: wallet-tasks-pipeline
```

Run `kanject aws pipeline up` once inside each service directory. A service with several Lambdas doesn’t need several pipelines — all of its entry projects deploy together into one stack. Draw the boundary where release cadence and ownership split.

> **Monorepo on your own runner?:** Use a matrix job with a path filter so only changed services redeploy — each service is its own matrix entry, and unchanged services skip. See *Workflows* for the daily command loop around deploys.

---
_Source: https://www.kanject.com/docs/cli-pipeline/ · Kanject Docs_
