Deploy AWS Lightsail Container Services

The aws:lightsail/containerService:ContainerService resource, part of the Pulumi AWS provider, provisions the compute and networking platform for running containerized applications in Lightsail. This guide focuses on three capabilities: compute capacity configuration, custom domain mapping, and private ECR registry access.

Container services require SSL/TLS certificates for custom domains and ECR repositories for private images. The examples are intentionally small. Combine them with your own container deployment configurations, which define images, ports, and environment variables separately from the service resource.

Create a container service with compute capacity

Most deployments start by defining compute capacity through power and scale settings, which determine the memory, vCPUs, and number of nodes available to your containers.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.lightsail.ContainerService("example", {
    name: "container-service-1",
    power: "nano",
    scale: 1,
    isDisabled: false,
    tags: {
        foo1: "bar1",
        foo2: "",
    },
});
import pulumi
import pulumi_aws as aws

example = aws.lightsail.ContainerService("example",
    name="container-service-1",
    power="nano",
    scale=1,
    is_disabled=False,
    tags={
        "foo1": "bar1",
        "foo2": "",
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lightsail"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := lightsail.NewContainerService(ctx, "example", &lightsail.ContainerServiceArgs{
			Name:       pulumi.String("container-service-1"),
			Power:      pulumi.String("nano"),
			Scale:      pulumi.Int(1),
			IsDisabled: pulumi.Bool(false),
			Tags: pulumi.StringMap{
				"foo1": pulumi.String("bar1"),
				"foo2": pulumi.String(""),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.LightSail.ContainerService("example", new()
    {
        Name = "container-service-1",
        Power = "nano",
        Scale = 1,
        IsDisabled = false,
        Tags = 
        {
            { "foo1", "bar1" },
            { "foo2", "" },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.lightsail.ContainerService;
import com.pulumi.aws.lightsail.ContainerServiceArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new ContainerService("example", ContainerServiceArgs.builder()
            .name("container-service-1")
            .power("nano")
            .scale(1)
            .isDisabled(false)
            .tags(Map.ofEntries(
                Map.entry("foo1", "bar1"),
                Map.entry("foo2", "")
            ))
            .build());

    }
}
resources:
  example:
    type: aws:lightsail:ContainerService
    properties:
      name: container-service-1
      power: nano
      scale: 1
      isDisabled: false
      tags:
        foo1: bar1
        foo2: ""

The power property selects a node size (nano through xlarge), controlling memory and vCPU allocation per node. The scale property sets how many nodes run in parallel. Together, these determine your service’s total compute capacity and monthly cost. The isDisabled property lets you pause the service without deleting it.

Map custom domains to the container service

To serve traffic on custom domains like www.example.com, you associate those domains with the service before deploying containers.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.lightsail.ContainerService("example", {publicDomainNames: {
    certificates: [{
        certificateName: "example-certificate",
        domainNames: ["www.example.com"],
    }],
}});
import pulumi
import pulumi_aws as aws

example = aws.lightsail.ContainerService("example", public_domain_names={
    "certificates": [{
        "certificate_name": "example-certificate",
        "domain_names": ["www.example.com"],
    }],
})
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lightsail"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := lightsail.NewContainerService(ctx, "example", &lightsail.ContainerServiceArgs{
			PublicDomainNames: &lightsail.ContainerServicePublicDomainNamesArgs{
				Certificates: lightsail.ContainerServicePublicDomainNamesCertificateArray{
					&lightsail.ContainerServicePublicDomainNamesCertificateArgs{
						CertificateName: pulumi.String("example-certificate"),
						DomainNames: pulumi.StringArray{
							pulumi.String("www.example.com"),
						},
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.LightSail.ContainerService("example", new()
    {
        PublicDomainNames = new Aws.LightSail.Inputs.ContainerServicePublicDomainNamesArgs
        {
            Certificates = new[]
            {
                new Aws.LightSail.Inputs.ContainerServicePublicDomainNamesCertificateArgs
                {
                    CertificateName = "example-certificate",
                    DomainNames = new[]
                    {
                        "www.example.com",
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.lightsail.ContainerService;
import com.pulumi.aws.lightsail.ContainerServiceArgs;
import com.pulumi.aws.lightsail.inputs.ContainerServicePublicDomainNamesArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new ContainerService("example", ContainerServiceArgs.builder()
            .publicDomainNames(ContainerServicePublicDomainNamesArgs.builder()
                .certificates(ContainerServicePublicDomainNamesCertificateArgs.builder()
                    .certificateName("example-certificate")
                    .domainNames("www.example.com")
                    .build())
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:lightsail:ContainerService
    properties:
      publicDomainNames:
        certificates:
          - certificateName: example-certificate
            domainNames:
              - www.example.com

The publicDomainNames property maps SSL/TLS certificates to domain names. You must create and validate the certificate in Lightsail before referencing it here. When you deploy a container with a public endpoint, Lightsail routes traffic from these domains to your application.

Pull images from Amazon ECR private repositories

Container services that use private ECR images need IAM permissions to authenticate and pull from those repositories.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const exampleContainerService = new aws.lightsail.ContainerService("example", {privateRegistryAccess: {
    ecrImagePullerRole: {
        isActive: true,
    },
}});
const example = exampleContainerService.privateRegistryAccess.apply(privateRegistryAccess => aws.iam.getPolicyDocumentOutput({
    statements: [{
        effect: "Allow",
        principals: [{
            type: "AWS",
            identifiers: [privateRegistryAccess.ecrImagePullerRole?.principalArn],
        }],
        actions: [
            "ecr:BatchGetImage",
            "ecr:GetDownloadUrlForLayer",
        ],
    }],
}));
const exampleRepositoryPolicy = new aws.ecr.RepositoryPolicy("example", {
    repository: exampleAwsEcrRepository.name,
    policy: example.apply(example => example.json),
});
import pulumi
import pulumi_aws as aws

example_container_service = aws.lightsail.ContainerService("example", private_registry_access={
    "ecr_image_puller_role": {
        "is_active": True,
    },
})
example = example_container_service.private_registry_access.apply(lambda private_registry_access: aws.iam.get_policy_document_output(statements=[{
    "effect": "Allow",
    "principals": [{
        "type": "AWS",
        "identifiers": [private_registry_access.ecr_image_puller_role.principal_arn],
    }],
    "actions": [
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer",
    ],
}]))
example_repository_policy = aws.ecr.RepositoryPolicy("example",
    repository=example_aws_ecr_repository["name"],
    policy=example.json)
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ecr"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lightsail"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
exampleContainerService, err := lightsail.NewContainerService(ctx, "example", &lightsail.ContainerServiceArgs{
PrivateRegistryAccess: &lightsail.ContainerServicePrivateRegistryAccessArgs{
EcrImagePullerRole: &lightsail.ContainerServicePrivateRegistryAccessEcrImagePullerRoleArgs{
IsActive: pulumi.Bool(true),
},
},
})
if err != nil {
return err
}
example := exampleContainerService.PrivateRegistryAccess.ApplyT(func(privateRegistryAccess lightsail.ContainerServicePrivateRegistryAccess) (iam.GetPolicyDocumentResult, error) {
return iam.GetPolicyDocumentResult(interface{}(iam.GetPolicyDocument(ctx, &iam.GetPolicyDocumentArgs{
Statements: []iam.GetPolicyDocumentStatement([]iam.GetPolicyDocumentStatement{
{
Effect: pulumi.StringRef(pulumi.String(pulumi.StringRef("Allow"))),
Principals: []iam.GetPolicyDocumentStatementPrincipal{
{
Type: "AWS",
Identifiers: interface{}{
privateRegistryAccess.EcrImagePullerRole.PrincipalArn,
},
},
},
Actions: []string{
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
},
},
}),
}, nil))), nil
}).(iam.GetPolicyDocumentResultOutput)
_, err = ecr.NewRepositoryPolicy(ctx, "example", &ecr.RepositoryPolicyArgs{
Repository: pulumi.Any(exampleAwsEcrRepository.Name),
Policy: pulumi.String(example.ApplyT(func(example iam.GetPolicyDocumentResult) (*string, error) {
return &example.Json, nil
}).(pulumi.StringPtrOutput)),
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var exampleContainerService = new Aws.LightSail.ContainerService("example", new()
    {
        PrivateRegistryAccess = new Aws.LightSail.Inputs.ContainerServicePrivateRegistryAccessArgs
        {
            EcrImagePullerRole = new Aws.LightSail.Inputs.ContainerServicePrivateRegistryAccessEcrImagePullerRoleArgs
            {
                IsActive = true,
            },
        },
    });

    var example = Aws.Iam.GetPolicyDocument.Invoke(new()
    {
        Statements = new[]
        {
            new Aws.Iam.Inputs.GetPolicyDocumentStatementArgs
            {
                Effect = "Allow",
                Principals = new[]
                {
                    new Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalArgs
                    {
                        Type = "AWS",
                        Identifiers = new[]
                        {
                            exampleContainerService.PrivateRegistryAccess.EcrImagePullerRole?.PrincipalArn,
                        },
                    },
                },
                Actions = new[]
                {
                    "ecr:BatchGetImage",
                    "ecr:GetDownloadUrlForLayer",
                },
            },
        },
    });

    var exampleRepositoryPolicy = new Aws.Ecr.RepositoryPolicy("example", new()
    {
        Repository = exampleAwsEcrRepository.Name,
        Policy = example.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.lightsail.ContainerService;
import com.pulumi.aws.lightsail.ContainerServiceArgs;
import com.pulumi.aws.lightsail.inputs.ContainerServicePrivateRegistryAccessArgs;
import com.pulumi.aws.lightsail.inputs.ContainerServicePrivateRegistryAccessEcrImagePullerRoleArgs;
import com.pulumi.aws.iam.IamFunctions;
import com.pulumi.aws.iam.inputs.GetPolicyDocumentArgs;
import com.pulumi.aws.ecr.RepositoryPolicy;
import com.pulumi.aws.ecr.RepositoryPolicyArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var exampleContainerService = new ContainerService("exampleContainerService", ContainerServiceArgs.builder()
            .privateRegistryAccess(ContainerServicePrivateRegistryAccessArgs.builder()
                .ecrImagePullerRole(ContainerServicePrivateRegistryAccessEcrImagePullerRoleArgs.builder()
                    .isActive(true)
                    .build())
                .build())
            .build());

        final var example = exampleContainerService.privateRegistryAccess().applyValue(_privateRegistryAccess -> IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
            .statements(GetPolicyDocumentStatementArgs.builder()
                .effect("Allow")
                .principals(GetPolicyDocumentStatementPrincipalArgs.builder()
                    .type("AWS")
                    .identifiers(_privateRegistryAccess.ecrImagePullerRole().principalArn())
                    .build())
                .actions(                
                    "ecr:BatchGetImage",
                    "ecr:GetDownloadUrlForLayer")
                .build())
            .build()));

        var exampleRepositoryPolicy = new RepositoryPolicy("exampleRepositoryPolicy", RepositoryPolicyArgs.builder()
            .repository(exampleAwsEcrRepository.name())
            .policy(example.applyValue(_example -> _example.json()))
            .build());

    }
}
resources:
  exampleContainerService:
    type: aws:lightsail:ContainerService
    name: example
    properties:
      privateRegistryAccess:
        ecrImagePullerRole:
          isActive: true
  exampleRepositoryPolicy:
    type: aws:ecr:RepositoryPolicy
    name: example
    properties:
      repository: ${exampleAwsEcrRepository.name}
      policy: ${example.json}
variables:
  example:
    fn::invoke:
      function: aws:iam:getPolicyDocument
      arguments:
        statements:
          - effect: Allow
            principals:
              - type: AWS
                identifiers:
                  - ${exampleContainerService.privateRegistryAccess.ecrImagePullerRole.principalArn}
            actions:
              - ecr:BatchGetImage
              - ecr:GetDownloadUrlForLayer

The privateRegistryAccess property enables ECR integration by creating an IAM role. Setting isActive to true generates a principalArn that you use in your ECR repository policy. The example shows how to grant the necessary pull permissions (BatchGetImage, GetDownloadUrlForLayer) to this principal.

Beyond these examples

These snippets focus on specific container service features: compute capacity sizing, custom domain mapping, and private registry authentication. They’re intentionally minimal rather than full container deployments.

The examples may reference pre-existing infrastructure such as SSL/TLS certificates for custom domains, ECR repositories for private images, and DNS configuration for domain routing. They focus on configuring the service platform rather than deploying actual containers.

To keep things focused, common container service patterns are omitted, including:

  • Container deployment configuration (images, ports, environment variables)
  • Service state management (isDisabled for pausing)
  • Regional placement (region property)
  • Load balancer and networking details

These omissions are intentional: the goal is to illustrate how each service feature is wired, not provide drop-in container deployment modules. See the Lightsail ContainerService resource reference for all available configuration options.

Let's deploy AWS Lightsail Container Services

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Custom Domains & SSL
Why can't I use custom domains with my container service?
You must create and validate an SSL/TLS certificate before configuring publicDomainNames. Custom domains won’t work without a valid certificate.
How many custom domains can I use with a container service?
You can specify up to four public domain names using publicDomainNames. If you don’t configure custom domains, the service provides a default domain accessible via the url property.
Configuration & Sizing
What are the available power specifications for container services?
The power parameter accepts: nano, micro, small, medium, large, or xlarge. Each power level determines the memory, vCPUs, and monthly price per node.
What does the scale parameter control?
The scale parameter specifies the number of allocated compute nodes for your container service. It’s a required integer value.
Can I temporarily disable my container service without deleting it?
Yes, set isDisabled to true. This parameter defaults to false when not specified.
Naming & Immutability
Can I rename my container service after creation?
No, the name property is immutable. To change the name, you must destroy and recreate the resource.
What are the naming requirements for container services?
Names must be 1 to 63 characters long and unique within each AWS Region in your Lightsail account.
Private Registry & ECR
How do I pull container images from Amazon ECR?
Configure privateRegistryAccess with ecrImagePullerRole.isActive set to true. Then create an ECR repository policy granting ecr:BatchGetImage and ecr:GetDownloadUrlForLayer permissions to the generated principalArn.
Where do I find the principal ARN for ECR access?
The principalArn output property contains the ARN needed to create trust relationships between your AWS account and the container service. Use this in your ECR repository policy.

Using a different cloud?

Explore containers guides for other cloud providers: