> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fjall.io/llms.txt
> Use this file to discover all available pages before exploring further.

# ECR Repository

> Provision an AWS ECR repository for container image storage with vulnerability scanning and lifecycle rules in Fjall.

## Overview

The ECR (Elastic Container Registry) Repository resource stores container images with vulnerability scanning and lifecycle management. It integrates with ECS and other AWS services for container deployments.

## Resource Class

```typescript theme={null}
import { Ecr } from "@fjall/components-infrastructure/lib/resources/aws/storage/ecr";
```

## Basic Usage

```typescript theme={null}
const repository = new Ecr(this, "AppRepository", {
  repositoryName: "my-app",
});
```

## Default Configuration

All ECR repositories include these defaults:

| Feature              | Default Value | Description                                          |
| -------------------- | ------------- | ---------------------------------------------------- |
| `imageScanOnPush`    | `true`        | Vulnerability scanning runs on every push            |
| `imageTagMutability` | `IMMUTABLE`   | Tags cannot be overwritten, push a new tag per build |
| `emptyOnDelete`      | `false`       | Images survive stack teardown                        |
| `removalPolicy`      | `RETAIN`      | Repository kept on stack deletion                    |

<Warning>
  Tags are immutable. You cannot re-push `:latest` over an existing image. Push
  a new, unique tag (for example a build number or content digest) on each
  deploy.
</Warning>

## Factory Pattern

### Using EcrFactory

```typescript theme={null}
const repoFactory = EcrFactory.build("AppRepo", {
  repositoryName: "my-app",
});

const repository = repoFactory(app, this);
```

### With StackBuilder

```typescript theme={null}
const repository = Ecr.build("AppRepo")(stackBuilder);
```

## Image Scanning

### Automatic Scanning

```typescript theme={null}
// Scanning enabled by default
const repository = new Ecr(this, "ScannedRepo");

// Images scanned on push automatically
// Results available in ECR console
```

### Scan Results Integration

```typescript theme={null}
// Create SNS topic for notifications
const scanTopic = new Topic(this, "ScanAlerts");

// EventBridge rule for scan findings
new Rule(this, "ScanRule", {
  eventPattern: {
    source: ["aws.ecr"],
    detailType: ["ECR Image Scan"],
    detail: {
      "repository-name": [repository.repositoryName],
      "scan-status": ["COMPLETE"],
      "finding-severity-counts": {
        CRITICAL: [{ numeric: [">", 0] }],
      },
    },
  },
  targets: [new SnsTopic(scanTopic)],
});
```

## Lifecycle Policies

### Basic Lifecycle

```typescript theme={null}
const repository = new Ecr(this, "ManagedRepo");

// Add lifecycle rule
repository.addLifecycleRule({
  description: "Keep last 10 images",
  maxImageCount: 10,
  rulePriority: 1,
});
```

### Advanced Lifecycle Rules

```typescript theme={null}
// Keep tagged images longer
repository.addLifecycleRule({
  description: "Expire untagged images",
  maxImageAge: Duration.days(7),
  tagStatus: TagStatus.UNTAGGED,
  rulePriority: 1,
});

// Keep production images
repository.addLifecycleRule({
  description: "Keep production images",
  tagPrefixList: ["prod", "release"],
  maxImageCount: 50,
  rulePriority: 2,
});

// Clean up old dev images
repository.addLifecycleRule({
  description: "Expire dev images after 30 days",
  tagPrefixList: ["dev"],
  maxImageAge: Duration.days(30),
  rulePriority: 3,
});
```

## Access Control

### Repository Policies

```typescript theme={null}
const repository = new Ecr(this, "SharedRepo");

// Allow cross-account access
repository.addToResourcePolicy(
  new PolicyStatement({
    principals: [new AccountPrincipal("123456789012")],
    actions: [
      "ecr:GetDownloadUrlForLayer",
      "ecr:BatchGetImage",
      "ecr:BatchCheckLayerAvailability",
    ],
  }),
);
```

### IAM Permissions

```typescript theme={null}
const repository = new Ecr(this, "AppRepo");

// Grant pull permissions
repository.grantPull(ecsTaskRole);

// Grant push permissions
repository.grantPullPush(codeBuildRole);

// Custom permissions
repository.grant(customRole, "ecr:DescribeImages", "ecr:ListImages");
```

## Integration with ECS

### Basic ECS Integration

```typescript theme={null}
const repository = new Ecr(this, "ServiceRepo");

const taskDefinition = new FargateTaskDefinition(this, "TaskDef");

taskDefinition.addContainer("app", {
  image: ContainerImage.fromEcrRepository(repository, "latest"),
  memoryLimitMiB: 512,
});
```

### With Image Tag Parameter

```typescript theme={null}
const imageTag = new StringParameter(this, "ImageTag", {
  parameterName: "/app/image-tag",
  stringValue: "latest",
});

const container = taskDefinition.addContainer("app", {
  image: ContainerImage.fromEcrRepository(repository, imageTag.stringValue),
});
```

## CI/CD Integration

### CodeBuild Integration

```typescript theme={null}
const repository = new Ecr(this, "BuildRepo");

const buildProject = new Project(this, "BuildProject", {
  environment: {
    buildImage: LinuxBuildImage.STANDARD_5_0,
    privileged: true, // For Docker
  },
  environmentVariables: {
    ECR_REPO_URI: {
      value: repository.repositoryUri,
    },
  },
});

// Grant push permissions
repository.grantPullPush(buildProject);
```

### GitHub Actions Integration

```typescript theme={null}
// Create OIDC provider for GitHub
const provider = new OpenIdConnectProvider(this, "GitHub", {
  url: "https://token.actions.githubusercontent.com",
  clientIds: ["sts.amazonaws.com"],
});

// Role for GitHub Actions
const githubRole = new Role(this, "GitHubActionsRole", {
  assumedBy: new OpenIdConnectPrincipal(provider, {
    StringEquals: {
      "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
      "token.actions.githubusercontent.com:sub":
        "repo:myorg/myrepo:ref:refs/heads/main",
    },
  }),
});

// Grant ECR permissions
repository.grantPullPush(githubRole);
```

## Encryption

<Note>
  The Fjall `Ecr` wrapper accepts only `repositoryName`. It does not expose
  `encryption` or `encryptionKey` props. The examples below use the raw CDK
  `Repository` construct directly. For a customer-managed key, instantiate
  `Repository` instead of `Ecr`.
</Note>

### Default Encryption

```typescript theme={null}
import { Repository, RepositoryEncryption } from "aws-cdk-lib/aws-ecr";

// AES-256 is the ECR default. Pass RepositoryEncryption.KMS for AWS-managed KMS.
const repository = new Repository(this, "EncryptedRepo", {
  encryption: RepositoryEncryption.KMS,
  lifecycleRules: [{ maxImageCount: 10 }],
});
```

### Customer Managed Key

```typescript theme={null}
const key = new Key(this, "RepoKey", {
  description: "ECR repository encryption key",
});

const repository = new Repository(this, "CustomEncryptedRepo", {
  encryption: RepositoryEncryption.KMS,
  encryptionKey: key,
});
```

## Outputs

The Ecr construct automatically creates these outputs:

```typescript theme={null}
const repository = new Ecr(this, "OutputRepo");

// Automatically exported:
// - RepositoryName: The repository name
// - Export name: ${id}RepositoryName
```

## Complete Example

```typescript theme={null}
import { Ecr } from "@fjall/components-infrastructure/lib/resources/aws/storage/ecr";
import { FargateTaskDefinition, ContainerImage } from "aws-cdk-lib/aws-ecs";
import { Project, LinuxBuildImage } from "aws-cdk-lib/aws-codebuild";

// Create repository
const repository = new Ecr(this, "ApplicationRepository", {
  repositoryName: `${props.appName}-${props.environment}`,
});

// Lifecycle management
repository.addLifecycleRule({
  description: "Keep 20 production images",
  tagPrefixList: ["prod"],
  maxImageCount: 20,
  rulePriority: 1,
});

repository.addLifecycleRule({
  description: "Expire untagged after 3 days",
  tagStatus: TagStatus.UNTAGGED,
  maxImageAge: Duration.days(3),
  rulePriority: 2,
});

// CI/CD build project
const buildProject = new Project(this, "DockerBuild", {
  source: Source.gitHub({
    owner: "myorg",
    repo: "myapp",
  }),
  environment: {
    buildImage: LinuxBuildImage.STANDARD_5_0,
    privileged: true,
    environmentVariables: {
      AWS_ACCOUNT_ID: { value: this.account },
      AWS_REGION: { value: this.region },
      ECR_REPO: { value: repository.repositoryUri },
    },
  },
  buildSpec: BuildSpec.fromObject({
    version: "0.2",
    phases: {
      pre_build: {
        commands: [
          "aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_REPO",
        ],
      },
      build: {
        commands: [
          "docker build -t $ECR_REPO:$CODEBUILD_RESOLVED_SOURCE_VERSION .",
          "docker tag $ECR_REPO:$CODEBUILD_RESOLVED_SOURCE_VERSION $ECR_REPO:latest",
        ],
      },
      post_build: {
        commands: [
          "docker push $ECR_REPO:$CODEBUILD_RESOLVED_SOURCE_VERSION",
          "docker push $ECR_REPO:latest",
        ],
      },
    },
  }),
});

// Grant permissions
repository.grantPullPush(buildProject);

// ECS task definition
const taskDefinition = new FargateTaskDefinition(this, "AppTask", {
  cpu: 256,
  memoryLimitMiB: 512,
});

taskDefinition.addContainer("app", {
  image: ContainerImage.fromEcrRepository(repository, "latest"),
  portMappings: [{ containerPort: 3000 }],
  logging: LogDrivers.awsLogs({
    streamPrefix: "app",
  }),
});

// Output repository URI
new CfnOutput(this, "RepositoryUri", {
  value: repository.repositoryUri,
  description: "ECR Repository URI",
});
```

## Best Practices

1. **Enable image scanning** (default) for security
2. **Use lifecycle rules** to control costs
3. **Tag images properly** for lifecycle management
4. **Use immutable tags** for production
5. **Implement least privilege** access
6. **Monitor repository size** and costs
7. **Use KMS encryption** for sensitive images

## Cost Optimisation

### Storage Costs

* **\$0.10 per GB/month** for storage
* Use lifecycle rules to remove old images
* Compress images using multi-stage builds

### Data Transfer

* **Free** within same region
* **\$0.09 per GB** for cross-region
* Use VPC endpoints to reduce costs

### Example Savings

```typescript theme={null}
// Aggressive cleanup for dev images
repository.addLifecycleRule({
  tagPrefixList: ["dev", "feature"],
  maxImageCount: 3, // Keep only last 3
  rulePriority: 1,
});

// Remove untagged immediately
repository.addLifecycleRule({
  tagStatus: TagStatus.UNTAGGED,
  maxImageAge: Duration.days(1),
  rulePriority: 2,
});
```

## Monitoring

### CloudWatch Metrics

```typescript theme={null}
// Repository size alarm
new Alarm(this, "RepoSizeAlarm", {
  metric: new Metric({
    namespace: "AWS/ECR",
    metricName: "RepositorySizeBytes",
    dimensionsMap: {
      RepositoryName: repository.repositoryName,
    },
  }),
  threshold: 10 * 1024 * 1024 * 1024, // 10 GB
  evaluationPeriods: 1,
});
```

### Image Push Events

```typescript theme={null}
// Notification on image push
new Rule(this, "ImagePushRule", {
  eventPattern: {
    source: ["aws.ecr"],
    detailType: ["ECR Image Action"],
    detail: {
      "action-type": ["PUSH"],
      "repository-name": [repository.repositoryName],
      "image-tag": ["latest"],
    },
  },
  targets: [new LambdaFunction(deployFunction)],
});
```

## Troubleshooting

### Common Issues

1. **Push denied**: Check IAM permissions and repository policy
2. **Scan failures**: Review scan findings in console
3. **Storage limits**: Implement lifecycle rules
4. **Pull rate limits**: Use VPC endpoints

### Debug Commands

```bash theme={null}
# Login to ECR
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin \
  123456789012.dkr.ecr.us-east-1.amazonaws.com

# Describe repository
aws ecr describe-repositories --repository-names my-app

# List images
aws ecr list-images --repository-name my-app
```

## Next Steps

<CardGroup cols={2}>
  <Card title="ECS Cluster" icon="server" href="/resources/compute/ecs-cluster">
    Run containers from your ECR images on Fargate or EC2.
  </Card>

  <Card title="S3 Bucket" icon="bucket" href="/resources/storage/s3-bucket">
    Store objects and static assets alongside your images.
  </Card>

  <Card title="Compute Factory" icon="layer-group" href="/patterns/compute-factory">
    Compose compute resources with the Fjall pattern factory.
  </Card>

  <Card title="Buildkite Stack" icon="pipeline" href="/patterns/buildkite-stack">
    Build and push images through a CI/CD pipeline.
  </Card>
</CardGroup>
