Every landing zone template is checked before it reaches CloudFormation. A schema mistake, a deprecated property, or an insecure default fails the command you just ran instead of surfacing halfway through a rollback, which matters most on a StackSet that is already rolling out to every account in your organization.
Two layers do the work:
- The offline rule set. CDK evaluates its bundled CloudFormation rules against every synthesized template. This runs locally and needs no AWS credentials.
- The CloudFormation pre-deployment checks. CDK sends each template to CloudFormation for the six pre-deployment checks: three errors (resource property syntax, resource name conflicts, S3 bucket emptiness on delete) and three warnings (service quota limits, AWS Config recorder conflicts, ECR repositories that still hold images). These run against a real account, so they need credentials for the phase's target account.
Commands
| Command | Account needed | What it runs |
|---|---|---|
pnpm run validate | None | The offline rule set across both phases in one pass |
pnpm run organization:validate | Management account | Offline rules plus the pre-deployment checks for that phase |
pnpm run landingzone:validate | Landing zone account | Offline rules plus the pre-deployment checks for that phase |
Run the credential-free check on your own machine or in a pre-commit hook:
pnpm run validate
To include the CloudFormation checks, assume a role in the target account first and run that phase's task:
pnpm run organization:validate # management account
pnpm run landingzone:validate # landing zone account
cdk validate is still behind the CDK CLI's --unstable flag. The generated tasks pass it for you, so the commands above are stable to script against even while the underlying command is not.
The feature flags
Two context flags in cdk.json turn validation on. Both are set by .projenrc.ts, so change them there and re-run pnpm exec projen rather than editing cdk.json:
{
"context": {
"@aws-cdk/core:validateAgainstDefaultRules": true,
"@aws-cdk/core:annotationsInValidationReport": true
}
}
validateAgainstDefaultRules promotes rule findings to errors, so an ERROR-level finding fails synthesis. annotationsInValidationReport folds construct-level warnings into the same report, so you read one list instead of two.
Acknowledging a finding
When a rule fires on something you deploy deliberately, acknowledge that specific finding in your CDK code rather than turning the rule set off. Acknowledgements are scoped to a construct and carry a reason, so the next person to read the code knows why the exception exists:
Validations.of(myConstruct).acknowledge({
id: 'CloudFormation-Validate::W2531',
reason: 'Runtime upgrade tracked in JIRA-1234',
});
The id is the finding ID from the validation report. Acknowledge the narrowest scope that covers it: a single construct rather than the whole stack, so an unrelated future resource does not inherit the exception.
Where validation runs in CI
Both generated workflows validate before they act, and both use the credentials already configured for that phase:
- The deployment workflow validates each phase immediately before deploying it. A validation failure fails the job, so an invalid template never reaches CloudFormation.
- The pull request workflow validates each phase before diffing it, so a broken template shows up as a failed check rather than a confusing diff.
See GitHub Actions Deployment for how both workflows are wired.
Validation is not a diff
Validation tells you whether a template is valid; it does not tell you what will change. Keep running pnpm run organization:diff and pnpm run landingzone:diff:all before a deploy that touches organization structure, SCPs, or StackSet targeting. The two answer different questions, and the pull request workflow runs both for exactly that reason.