---
title: "cdkd deploy: checks on the cloud assembly"
description: "What cdkd deploy checks in a synthesized cloud assembly before it starts, which paths it refuses, and what it trusts in an assembly you did not synthesize."
---

# cdkd deploy: checks on the cloud assembly

The cloud assembly is the directory that synthesis writes, usually `cdk.out`.
It holds the templates, the asset manifests and the files to upload.
`cdkd deploy` checks its nested templates and file paths right after
synthesis, before it expands a macro, publishes an asset, takes a lock or
creates a resource.

An assembly that the CDK generated passes every check on this page. The checks
catch an assembly that was edited by hand or produced by another tool. One
malformed stack stops the whole run with exit code `1`, and that includes
`--dry-run`.

```bash
cdkd deploy MyStack                 # synthesizes, checks, then deploys
cdkd deploy -a cdk.out MyStack      # checks an assembly that already exists
cdkd deploy -a cdk.out 'MyStage/*'  # a Stage's stacks, from the app's cdk.out
```

This page is part of
[cdkd deploy: safety & compatibility flags](cli-deploy-safety.md).

## Cyclic nested templates are refused

cdkd refuses an assembly in which a nested stack's template includes itself,
directly or through other nested stacks:

```text
SynthesisError: The nested template tree under stack Parent contains a
cycle: Child (/path/to/cdk.out/child.json) then Loop
(/path/to/cdk.out/child.json). Nested stack Loop (declared in stack
Parent~Child) resolves to a template that is already on that nesting chain,
so its Metadata['aws:asset:path'] closes a cycle. CDK emits an acyclic nested
template tree with relative asset paths, so this indicates the synth output was
hand-modified or generated by a non-CDK toolchain. Refusing to start the
deploy; nothing has been published or provisioned.
```

A parent template points at each nested stack's template through the nested
stack resource's `Metadata['aws:asset:path']`. cdkd follows those paths from
every stack it is about to deploy, dependencies included. It refuses when a
path leads to a template that is already on the same chain from parent to
child.

### Edge cases

- **Two nested stacks that name the same template** are a shared child, and
  that is allowed. Only a repeat along one chain from the root to a child is
  refused.
- **A `Condition` on the nested stack is not evaluated.** A template that
  includes itself behind a condition is refused too, as it is by
  [`cdkd diff --recursive`](cli-diff.md).
- **An absolute `aws:asset:path`** is refused, and the message says
  `which is absolute`. The CDK always writes nested templates into the output
  directory.
- **A very large tree** is refused: more than 512 levels deep, or more than
  10,000 nested stacks.
- **A stack you are not deploying** is not checked.

## Paths that leave the assembly directory

cdkd refuses a path in the assembly that points outside the assembly. For a
nested template, that is an `aws:asset:path` that resolves outside the
directory its template sits in:

```text
SynthesisError: The nested template tree under stack Parent has nested stack
Child (reached through Child (/path/to/cdk.out/child.json)) with
Metadata['aws:asset:path']=../../etc/passwd which resolves to
/etc/passwd, outside /path/to/cdk.out. CDK emits assembly paths that stay
inside the assembly directory; one that leaves it indicates the synth output
was hand-modified or generated by a non-CDK toolchain. Refusing to start the
deploy; nothing has been published or provisioned.
```

A path that leaves the directory only through a symbolic link is refused the
same way, and the message names the link's target.

cdkd holds every other path the assembly supplies to the same rule, because it
would otherwise act on the file:

| Path in the assembly | What cdkd does with the file |
| --- | --- |
| A nested assembly's `directoryName` | Reads that directory's `manifest.json` |
| A stack's `templateFile` | Deploys whatever parses as its template |
| An asset manifest's `file` | Reads the list of assets to publish |
| A file asset's `source.path` | Zips it and uploads it to the bucket the manifest names |
| A Docker asset's `source.directory` | Sends it to the image build as the context |
| A stack's metadata side file | Reads its annotations |
| A stack name, under `cdkd synth --verbose` | Writes `<name>.template.json` there |

### Asset source paths

The two asset rows, `source.path` and `source.directory`, are measured against
the app's output directory (`cdk.out`). That is because a `Stage`'s asset
manifest legitimately points up to `../asset.<hash>`. cdkd treats a relative
path and an absolute path differently:

| The asset source path is | What cdkd does |
| --- | --- |
| Absolute, inside the output directory | Accepts it; every staged asset is there |
| Absolute, outside the output directory | Accepts it with a warning |
| The output directory itself, by any spelling | Accepts it with a warning |
| Relative, escaping the output directory | Refuses |

The warning for an outside path names the directory and the destination. When
the path names the output directory itself, the whole assembly becomes the
asset. No `cdk synth` emits that.

> [!WARNING]
> An absolute asset path outside the output directory is uploaded. cdkd
> accepts it because `cdk synth --no-staging` produces such paths, and the
> warning is the only protection. An assembly you did not synthesize can name
> any directory your user can read and upload it to a bucket the same manifest
> names, with your credentials.

### Pointing `-a` at a Stage's directory

Pointing `-a` at a Stage's sub-assembly
(`cdkd deploy -a cdk.out/assembly-MyStage`) makes cdkd refuse that Stage's own
`../asset.<hash>`, because the asset is outside the directory you named. Point
`-a` at the app's `cdk.out` and select the stack by its display path instead:

```bash
cdkd deploy -a cdk.out 'MyStage/*'
```

### The same rule in `cdkd local`

The `cdkd local` commands that mount Lambda code (`invoke`, `start-api`,
`start-alb`, `start-cloudfront`) apply the same rule to `aws:asset:path`.
They refuse a relative path that escapes and accept an absolute one with the
warning. [Local Execution](local-emulation.md) lists which other paths are
covered.

## A pre-synthesized assembly is trusted input

Outside the path rules above, cdkd uses an asset manifest as it is written,
as the CDK CLI does. So an assembly can make cdkd run a command, read a file
or write a file on your machine.

> [!IMPORTANT]
> Deploying a pre-synthesized assembly can run code from it. A Docker asset
> may declare `source.executable` instead of a Dockerfile. Pointing `-a` at an
> assembly you did not produce is the same decision as running someone else's
> build output. Synthesize it yourself, or read it first.

cdkd warns about four kinds of manifest value that reach outside the assembly.

### A command cdkd runs: `source.executable`

cdkd runs the value on your machine as a command line. It warns on every
command that builds a Docker asset and names the command. Every `cdkd local`
command that builds such an asset runs the executable too, and prints the
warning first. cdkd announces each distinct command once per run, and repeats
go to `--verbose`.

### Host paths the image build reads

These are `dockerFile`, `dockerBuildContexts`, `dockerBuildSecrets`,
`dockerBuildSsh`, `cacheFrom` and `cacheTo`. cdkd warns when the path is
outside the output directory. A path inside the build context usually gets no
warning.

A build secret, an SSH key or a cache directory is a host path the template
never shows. Reading the template therefore does not tell you what a deploy
touches.

### Host paths the image build writes

A `dest=` in `dockerOutputs` or in a cache option makes the build write to
that host path. cdkd warns when the path is outside the output directory,
wherever the build context is.

Whether a cache option reads or writes follows its key. A `cacheFrom` that
carries a `dest=` is a write, and a `cacheTo` that carries a `src=` is a read.

### Where assets are uploaded

cdkd uploads to `dest.bucketName` and to the ECR repository the manifest
names, with your credentials. It warns once per name when the name is neither
shaped like a CDK bootstrap resource nor managed by cdkd.

The check looks at the name only. A bucket named like a CDK bootstrap bucket
for your account can still belong to someone else.

## A Stage that failed to load stops every command

When cdkd cannot read a `Stage`'s own `manifest.json`, it stops, as the AWS
CDK CLI does. That happens when the Stage was never synthesized or its
directory is gone.

```text
Stage MyStage failed to load: ENOENT reading assembly-MyStage/manifest.json.
Every stack under it is missing from the cloud assembly, so cdkd will not act
on the app. Re-synthesize the app so the Stage is written, or point --app at a
complete cloud assembly.
```

cdkd stops whatever you selected, including a stack outside the Stage, and
`cdkd destroy` stops too. To reach a deployed stack by name without the app,
use `cdkd state destroy '<stack>'`. `cdkd scrub` exits `2`
(`SCRUB_STAGE_LOAD_FAILED`) for a Stage that failed to load.

Any other refusal under a Stage aborts the run with the innermost Stage named
ahead of it (`Stage MyStage: Stack MyStage-Api ...`).

## Related

- [cdkd deploy: safety & compatibility flags](cli-deploy-safety.md): what each
  deploy refusal means
- [`cdkd diff`](cli-diff.md): the same nested-template check under
  `--recursive`
- [Local Execution](local-emulation.md): the path rules in `cdkd local`
- [cdkd deploy safety internals](cli-deploy-safety-internals.md#cloud-assembly-checks):
  the exact rules of the nested-template walk and the asset path checks
