Kosli Capture is an early-access, opinionated drop-in. The Lambda, Terraform, config schema, and CLI flags it wraps may still change. Reach out to the Kosli team before rolling it into production, or with any questions and comments.
- Deploy Kosli Capture into an AWS account
- Configure what it snapshots using tag-based include/exclude rules and env-name templates
- Understand how it handles ECS, EKS, Lambda, and S3
- Change its config at runtime without a redeploy
- Handle the EKS onboarding and networking caveats
Prerequisites
- Access to an AWS account and permissions to run Terraform / CloudFormation in it (an IAM role, ECR repo, Lambda function, EventBridge rule, and a private S3 state bucket will be created).
- Docker and
makeon the machine you deploy from (used to build the Lambda container image and push it to ECR). Plainterraform applyafter the first deploy does not need Docker. - A Kosli API token with permission to create environments, stored as an SSM SecureString parameter (default name
/kosli/api-token). - Your Kosli organization name.
How Kosli Capture works
Kosli Capture is a scheduled Lambda that runs every few minutes inside your AWS account. Each tick it re-derives the full picture from scratch — no state is carried between runs — so a dropped or throttled tick is simply superseded by the next one.kosli CLI to talk to Kosli, so there is nothing to install into your resources themselves.
Two things follow from this design:
- Config is policy, not inventory.
config.yamlnever names individual resources. It sets tag filters, naming templates, and per-type toggles. Adding a new cluster does not need a config change. - Failures are localized. A single failing resource never stops the rest of the run — the failure is logged and reported as a
gap, and the other snapshots complete.
Deploy Kosli Capture
Clone the repository and deploy from its root:/kosli/api-token):
make bootstrap to stand up the Terraform state bucket (via CloudFormation, because the state bucket cannot be managed by the state it stores) and the ECR repo:
make deploy computes a content-hash image tag from the handler, config, and CLI version, so a re-run only rebuilds and repoints the function when something has actually changed.
Once the first run completes (every two minutes by default), you should see Kosli environments appear in the Kosli app under Environments, named according to the template in config.yaml.
Configure what gets snapshotted
config.yaml at the repo root defines the policy Kosli Capture applies. It has three parts: naming, scope, and types.
Naming
Within the template,
{prefix} itself resolves: kosli:prefix tag on the resource → types.<type>.prefix → naming.prefix → aws-{account_id}. Keep {type} in the template — env names are unique per Kosli org and a bucket can share a name with a cluster.
Scope and tag filters
Every type block accepts the same tag-filter fields, combined with the globalscope:
- include = global ∪ per-type pairs; a resource must match all of them (empty = opt-out mode: everything unless excluded).
- exclude = global ∪ per-type pairs; matching any pair excludes the resource.
scope.regions. When you scan multiple regions, add {region} to naming.template so each region gets its own env — a Kosli env holds one full-state snapshot, so overwriting the same name from different regions would clobber it each run.
Per-type toggles
s3 environment fingerprints bucket content (for deployment tracking), not a bucket inventory. Most accounts have many buckets that are not deployment artifacts — Terraform state buckets, CloudFormation staging buckets, log buckets. Snapshotting all of them would be noise. types.s3.include_tags must therefore be non-empty; otherwise Kosli Capture records a gap and skips S3 entirely.
Grouping Lambdas or ECS clusters by tag
Sometimes several Lambda functions or ECS clusters should be reported into the same Kosli environment — for example, all the Lambdas that make up one application. Usegrouping to map an exact env name to a set of tag pairs:
snapshot lambda --function-names a,b,c / snapshot ecs --clusters a,b,c). Unmatched resources fall through to the default behavior — Lambda’s granularity, ECS’s per-cluster env — unchanged. The first matching entry wins, and an entry with no tags never matches, so a typo cannot sweep in everything.
Grouping is only offered where the snapshot command reports a set in one call. S3 (--bucket is a single bucket) and per-cluster EKS have no such flag, so grouping does not apply to them — pointing several of those at one env name would just overwrite it each run.
EKS: whole-cluster or per-namespace
By default each discovered EKS cluster gets one Kosli environment. Clusters listed undertypes.eks.namespaces are instead reported per namespace — useful when one cluster hosts multiple applications or teams.
- List → derived env names (
{name}becomes<cluster>-<ns>). Entries can be regex patterns (kosli --namespacessupports them). - Map → explicit
namespace: env-namepairs. Preferred for regex patterns because the derived name would otherwise be sanitized. Explicit map entries are precedence level 1 — they beat every tag override.
Change config without a redeploy
Kosli Capture can read its policy from three places, controlled by the Terraform variableconfig_source:
Runtime sources fail closed: a failed fetch reuses the warm sandbox’s last-known-good config or fails the run — Kosli Capture never silently falls back to the baked copy, because stale policy could re-include resources you meant to exclude.
EKS caveats — the honest part
EKS is the one type where a drop-in cannot do everything for you. Two things to be aware of: Authorization. A newly discovered cluster has no access entry for the Kosli Capture role, so its snapshot fails withUnauthorized. You have two options:
eks_onboarding.mode: none— your cluster-creation pipeline is responsible for adding an access entry for the Kosli Capture role. Recommended when clusters are already provisioned by an existing IaC platform team.eks_onboarding.mode: self(withenable_eks_self_onboarding = truein Terraform) — Kosli Capture grants itselfAmazonEKSViewPolicyon each newly discovered cluster. The IAM permission for this is fenced witheks:principalArn/eks:policyArnconditions so Kosli Capture can only grant that one policy to its own role, and to nothing else.
API or API_AND_CONFIG_MAP for either option to work.
Networking. Clusters with a private-only API endpoint are unreachable from a Lambda outside their VPC. Kosli Capture detects this via DescribeCluster and reports it as a gap instead of failing the whole run. To cover private clusters, deploy Kosli Capture inside the VPC with vpc_subnet_ids and vpc_security_group_ids — one deployment per isolated VPC.
Verify it is working
Once deployed, three places tell you Kosli Capture is doing its job:- CloudWatch Logs for the Lambda function — each run logs the scan start, the resources it discovered vs. put in scope, the env names it derived, any gaps, and any errors. Bump verbosity at runtime (no rebuild) with the Terraform variable
debug_level = "debug"or"trace".tracealso passes--debuginto the underlyingkosliCLI. The API token is never logged. - Kosli Environments — new environments appear under Environments with names from your template. Each snapshot shows the running artifacts and their fingerprints, exactly as if you had reported them by hand.
kosli list snapshotsandkosli get snapshot— query environments from the CLI.
Known limitations
A few things worth calling out explicitly:- Scaling events are off by default. Kosli does not create a new snapshot for a change in replica count only — the workload’s provenance has not changed. Turn on
include_scaling: true(globally or per type) if you want scaling to produce new snapshots. - Renaming an env-name tag creates a new env, not a rename. Kosli Capture cannot see the previous value; it just derives the new name and creates the env on first sight. The old env remains until you archive it.
- One region per deploy by default. List
scope.regionsto fan out across regions, and add{region}to your naming template so each region gets its own env. - Kosli Capture excludes itself from Lambda discovery, so its own Lambda never shows up in your Kosli environments.
- EventBridge triggers are not enabled by default. The five-minute schedule is the backbone — EKS pods do not emit AWS events, so schedule-driven scanning is the common denominator. You can additionally invoke the same function from EventBridge rules (ECS task state change, CloudTrail
CreateCluster/CreateBucket) for lower-latency updates on the AWS-native types; every run re-derives truth, so no payload handling is needed on the trigger.
What you’ve accomplished
You have dropped a single stack into an AWS account and are now snapshotting ECS clusters, EKS clusters, Lambda functions, and tagged S3 buckets into Kosli — without deploying a reporter into each resource, without pre-creating environments, and without maintaining an inventory list in config. From here you can:- Query your environments and diff snapshots to see what changed
- Trace a running artifact back to its git commit
- Attach environment policies so Kosli evaluates each snapshot against your compliance requirements
- Compare against the more traditional per-resource reporter approach in Report AWS environments to Kosli