token-injectable-docker-builder
Version:
The TokenInjectableDockerBuilder is a flexible AWS CDK construct that enables the usage of AWS CDK tokens in the building, pushing, and deployment of Docker images to Amazon Elastic Container Registry (ECR). It leverages AWS CodeBuild and Lambda custom re
257 lines (256 loc) • 10.5 kB
TypeScript
import { IVpc, ISecurityGroup, SubnetSelection } from 'aws-cdk-lib/aws-ec2';
import { ContainerImage } from 'aws-cdk-lib/aws-ecs';
import { DockerImageCode } from 'aws-cdk-lib/aws-lambda';
import { ILogGroup } from 'aws-cdk-lib/aws-logs';
import { Construct } from 'constructs';
import { TokenInjectableDockerBuilderProvider } from './provider';
/**
* Properties for the `TokenInjectableDockerBuilder` construct.
*/
export interface TokenInjectableDockerBuilderProps {
/**
* The path to the directory containing the Dockerfile or source code.
*/
readonly path: string;
/**
* Build arguments to pass to the Docker build process.
* These are transformed into `--build-arg KEY=VALUE` flags.
* @example
* {
* TOKEN: 'my-secret-token',
* ENV: 'production'
* }
*/
readonly buildArgs?: {
[key: string]: string;
};
/**
* The ARN of the AWS Secrets Manager secret containing Docker login credentials.
* The secret must store a JSON object: `{"username":"...","password":"..."}`.
* Must be in the same region as the stack.
*
* @default - No Docker Hub login.
*/
readonly dockerLoginSecretArn?: string;
/**
* The VPC in which the CodeBuild project will be deployed.
*
* @default - CodeBuild uses public internet.
*/
readonly vpc?: IVpc;
/**
* Security groups attached to the CodeBuild project.
*
* @default - No security groups attached.
*/
readonly securityGroups?: ISecurityGroup[];
/**
* Subnet selection within the VPC.
*
* @default - All subnets in the VPC.
*/
readonly subnetSelection?: SubnetSelection;
/**
* Custom commands to run during the install phase of CodeBuild.
*
* @default - No additional install commands.
*/
readonly installCommands?: string[];
/**
* Custom commands to run during the pre_build phase of CodeBuild.
*
* @default - No additional pre-build commands.
*/
readonly preBuildCommands?: string[];
/**
* Whether to enable KMS encryption for the ECR repository.
*
* @default false
*/
readonly kmsEncryption?: boolean;
/**
* File paths in the Docker directory to exclude from the build asset.
* Falls back to `.dockerignore` if present.
*
* @default - No file path exclusions.
*/
readonly exclude?: string[];
/**
* Name of the Dockerfile (passed as `-f`).
*
* @example 'Dockerfile.production'
* @default 'Dockerfile'
*/
readonly file?: string;
/**
* When `true`, disables Docker layer caching.
*
* @default false
*/
readonly cacheDisabled?: boolean;
/**
* CloudWatch log group for CodeBuild build logs.
*
* @default - CodeBuild default logging.
*/
readonly buildLogGroup?: ILogGroup;
/**
* Target platform for the Docker image.
*
* @default 'linux/amd64'
*/
readonly platform?: 'linux/amd64' | 'linux/arm64';
/**
* Shared provider for the custom resource Lambdas.
*
* Pass `TokenInjectableDockerBuilderProvider.getOrCreate(this, { queryInterval })`
* if you need a non-default query interval. Otherwise, the construct will
* call `getOrCreate(this)` itself and reuse the per-stack singleton.
*
* @default - Per-stack singleton provider, created on first use.
*/
readonly provider?: TokenInjectableDockerBuilderProvider;
/**
* ECR pull-through cache repository prefixes to grant pull access to.
*
* @example ['docker-hub', 'ghcr']
* @default - No pull-through cache access.
*/
readonly ecrPullThroughCachePrefixes?: string[];
/**
* When `true`, creates a CloudWatch log group outside of CloudFormation
* (`/docker-builder/<projectName>`) and directs CodeBuild output there.
* Survives stack rollbacks for debugging. 7-day retention.
*
* @default false
*/
readonly retainBuildLogs?: boolean;
/**
* Additional AWS regions to replicate the built image to via ECR's
* native registry replication. The image is pushed to the primary
* region's ECR as usual; ECR asynchronously replicates the same
* `repositoryName` + `imageTag` to each region listed here.
*
* Consumers in another region (a Lambda in `us-west-2` referencing an
* image built in `us-east-1`) can use `dockerImageCodeFor(region)` or
* `containerImageFor(region)` to import the replicated image.
*
* The custom resource waits for replication to complete before
* signalling deploy-complete, so downstream stacks can safely deploy
* immediately after.
*
* **Caveats:**
* - Cross-region replication is not supported between AWS partitions.
* - Replicas do **not** inherit the primary's encryption (defaults to
* AES-256), lifecycle policies, or repository policies.
* - Replicated repositories persist on stack deletion — AWS does not
* auto-delete them. Clean up manually via the ECR console / CLI if
* needed.
* - Both the builder stack and any consumer stack in another region
* must set `crossRegionReferences: true` for the image tag to flow.
* - Stacks must have a concrete region (`env: { account, region }`),
* not the env-agnostic default.
*
* @example ['us-west-2', 'eu-west-1']
* @default [] - no replication
*/
readonly replicaRegions?: string[];
}
/**
* A CDK construct to build and push Docker images to an ECR repository using
* CodeBuild and Lambda custom resources, **then** retrieve the final image tag
* so that ECS/Lambda references use the exact built image.
*/
export declare class TokenInjectableDockerBuilder extends Construct {
/** The ECR repository that stores the resulting Docker image. */
private readonly ecrRepository;
/** ECS-compatible container image reference (primary region). */
readonly containerImage: ContainerImage;
/** Lambda-compatible DockerImageCode reference (primary region). */
readonly dockerImageCode: DockerImageCode;
/** The ECR repository name — preserved across replica regions. */
readonly repositoryName: string;
/**
* The resolved image tag (CFN token; available at deploy time).
*
* Safe to use anywhere the consumer is in the **same region** as the
* builder (same stack or different stack). For cross-region consumers
* use `imageTagPlain` — `imageTag` would trigger CDK's
* `CrossRegionExportWriter` and wedge on any tag change.
*/
readonly imageTag: string;
/**
* The deterministic image tag as a plain synth-time string (no CFN token).
*
* Same value as `imageTag` but resolved immediately — useful for cross-region
* consumers, where the CFN-token form would trigger CDK to auto-create a
* `CrossRegionExportWriter`. That writer has an over-strict safety check
* that fails any update where the tag value changes (i.e. every real code
* change), wedging the stack in `UPDATE_ROLLBACK_FAILED`.
*
* `containerImageFor` and `dockerImageCodeFor` use this string automatically
* when the consumer region differs from the primary region, so callers
* normally don't reference this property directly.
*/
readonly imageTagPlain: string;
private readonly primaryRegion;
private readonly accountId;
private readonly replicaRegions;
constructor(scope: Construct, id: string, props: TokenInjectableDockerBuilderProps);
/**
* Format the ECR repository URI for a given region. The region must
* be either the primary region or one of `replicaRegions`.
*/
repositoryUriFor(region: string): string;
/**
* Import the replicated repository as an ECS-compatible
* `ContainerImage` in a consumer scope (typically a stack in `region`).
*
* The consumer's stack must have `crossRegionReferences: true` when
* `region` differs from the builder's region.
*
* Cross-stack consumers (same region or different region) receive
* `imageTagPlain` (a synth-time string) rather than `imageTag` (a CFN
* token). Using the token would cause CDK to auto-create either a
* `CrossRegionExportWriter` (cross-region) or a cross-stack
* `Fn::Export` / `Fn::ImportValue` (same-region). Both wedge any deploy
* that changes the imageTag value, because CFN refuses to "update an
* export in use" or runs the writer's "Some exports have changed" check.
* Same-stack callers keep the token form so the Lambda still has a CFN
* dependency on the build trigger CR.
*/
containerImageFor(scope: Construct, region: string): ContainerImage;
/**
* Import the replicated repository as a Lambda-compatible
* `DockerImageCode` in a consumer scope (typically a stack in `region`).
*
* The consumer's stack must have `crossRegionReferences: true` when
* `region` differs from the builder's region.
*
* Cross-stack consumers (same region or different region) receive
* `imageTagPlain` (a synth-time string) rather than `imageTag` (a CFN
* token). See `containerImageFor` for the rationale.
*/
dockerImageCodeFor(scope: Construct, region: string): DockerImageCode;
/**
* Pick which tag form to hand to a cross-stack consumer:
* - same stack as the builder → CFN-token form (`imageTag`), preserves
* the CFN dependency from the consumer onto the build trigger CR.
* - different stack → synth-time plain string (`imageTagPlain`),
* so CDK doesn't auto-generate a cross-stack export/import or a
* `CrossRegionExportWriter`. Both would wedge on tag changes.
*
* For cross-stack same-region consumers in the same CDK app we also wire
* an explicit `Stack.addDependency`, since dropping the CFN export removes
* the implicit deploy ordering. Cross-region consumers in the same app
* are ordered by their CDK Stage / pipeline; cross-app consumers (e.g.
* separate pipelines) own their own orchestration.
*/
private tagForScope;
private importRepoFor;
private assertRegionIsKnown;
private validateReplicaRegions;
private grantEcrAccess;
private grantPullThroughCacheAccess;
private grantBuildLogsAccess;
}