In the AWS docs there's a detailed guide on how to grant ECS EC2 & Fargate launch type Tasks access to private Registries. Derived from that there are 4 steps to take:
- Obtain Token or credentials to access private Container Registry
- Create AWS Secrets Manager Secret containing Token/Creds to private Registry
- Craft Task Execution Role (using aws.iam.Role) containing inlinePolicy for private Container Registry access
- Enhance awsx.ecs.FargateService to use our Task Execution Role & Secret ARN in repositoryCredentials
0. Obtain Token or credentials to access private Container Registry
If you don't already have them, you'll need to create an Access Token or credentials inside our private Registry so that an external service is able to access it. With GitHub Container Registry for example we need to create a Personal Access Token (see docs here) using the read:packages
scope as a minimum:

1. Create AWS Secrets Manager Secret containing Token/Creds to private Registry
Now head over to the AWS Secrets Manager console at https://console.aws.amazon.com/secretsmanager/ and create a new Secret via the Store a new secret
button. In the GUI choose Other type of secrets
and Plaintext
- and then fill in your private Registry credentials as JSON:
{
"username": "yourGitHubUserNameHere",
"password": "yourGitHubPATHere"
}
Select Next
and provide a Secret Name like githubContainerRegistryAccess
. Hit Next again and leave Disable automatic rotation
as the default. Next again and hit Store
to create the Secret. Finally copy the Secret ARN like arn:aws:secretsmanager:awsRegionHere:yourAccountIdHere:secret:githubContainerRegistryAccess-randomNumberHere
to your notepad or editor for later reference.
2. Craft Task Execution Role (using aws.iam.Role) containing inlinePolicy for private Container Registry access
As the docs tell us we need to add the permission to access the Secrets Manager Secret as Inline Policy to the Fargate Tasks Execution Role. Now as the new awsx.ecs.FargateService
automatically creates such a Task Execution Role, but we can't access it afterwards really, we need to create the whole thing ourselves. To create the aws.iam.Role
we can have a look into the Pulumi docs about it. A Pulumi aws.iam.Role
consists of multiple components, and we need 3 of them:
assumeRolePolicy
: I didn't really wanted to define this myself, but the aws.iam.Role
can't be created without it. It's crucial to choose "sts:AssumeRole"
as the Action
and "ecs-tasks.amazonaws.com"
as the Service Principal.
inlinePolicies
: This array will take our InlinePolicy which the Task needs to access the Secrets Manager Secret (aka the whole point of this Role creation here). It's exactly the same as described in the AWS docs - really keep an eye on the correct arn
names inside Resources
. One of the two is exactly our Secrets Manager Secret ARN!
managedPolicyArns
contains the default Policies attached to a Fargate Task by Pulumi (I simply had a look into the AWS Console to find their arns).
Here's the Pulumi code we need for the InlinePolicy to be defined correctly:
const taskExecutionRole = new aws.iam.Role("microservice-api-spring-boot-execution", {
assumeRolePolicy: {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}, inlinePolicies: [
{
name: "ghcr-secret-access",
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Action: [
"kms:Decrypt",
"secretsmanager:GetSecretValue"
],
Resource: [
"arn:aws:secretsmanager:awsRegionHere:yourAccountIdHere:secret:githubContainerRegistryAccess-randomNumberHere",
"arn:aws:kms:awsRegionHere:yourAccountIdHere:key/key_id"
]
}]
})
},
],
managedPolicyArns: [
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
]
});
3. Enhance awsx.ecs.FargateService to use our Task Execution Role & Secret ARN in repositoryCredentials
Now this is the final step. We need to use our created Task Execution Role and attach it to our awsx.ecs.FargateService
using the executionRole
parameter. And we also need to provide our Secrets Manager Secret ARN (again) as repositoryCredentials:credentialsParameter
to our awsx.ecs.FargateService
. This looks somehow like this:
// Define Container image published to the GitHub Container Registry
const service = new awsx.ecs.FargateService("microservice-api-spring-boot", {
taskDefinitionArgs: {
containers: {
blueprint_helloworld: {
image: "ghcr.io/jonashackt/microservice-api-spring-boot-private:latest",
memory: 768,
portMappings: [ albListener ],
// Access private GitHub Container Registry: we need to provide the Secret ARN as repositoryCredentials
// see https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/ecs/#Container-repositoryCredentials
repositoryCredentials: {
credentialsParameter: "arn:aws:secretsmanager:awsRegionHere:yourAccountIdHere:secret:githubContainerRegistryAccess-randomNumberHere",
}
},
},
executionRole: taskExecutionRole,
},
desiredCount: 2,
});
Now a pulumi up
should bring up your Fargate Tasks as expected, since they're now able to pull the container images from the private GitHub Container Registry. Inside the AWS ECS Cluster view you should see your running Tasks:
