Service
The Service
construct is a higher level CDK construct that simplifies the deployment of containerized applications. It provides a simple way to build and deploy your app to AWS with these features:
- Deployment to an ECS Fargate cluster with an Application Load Balancer as the front end.
- Auto-scaling based on CPU and memory utilization and per-container request count.
- Directly referencing other AWS resources in your app.
- Configuring custom domains for your website URL.
Quick Start
To create a service, set path
to the directory that contains the Dockerfile.
import { Service } from "sst/constructs";
new Service(stack, "MyService", {
path: "./service",
port: 3000,
});
Here's an example of the Dockerfile
for a simple Express app.
FROM node:18-bullseye-slim
COPY . /app
WORKDIR /app/
RUN npm install
ENTRYPOINT ["node", "app.mjs"]
And the app.mjs
would look like this:
import express from "express";
const app = express();
app.get("/", (req, res) => {
res.send("Hello world");
});
app.listen(3000);
The Docker container uses the Node.js 18 slim image in this instance, installs the dependencies specified in the package.json
, and then starts the Express server.
When you run sst deploy
, SST does a couple things:
- Runs
docker build
to build the image - Uploads the image to Elastic Container Registry (ECR)
- Creates a VPC if one is not provided
- Launches an Elastic Container Service (ECS) cluster in the VPC
- Creates a Fargate service to run the container image
- Creates an Auto Scaling Group to auto-scale the cluster
- Creates an Application Load Balancer (ALB) to route traffic to the cluster
- Creates a CloudFront Distribution to allow configuration of caching and custom domains
Working locally
To work on your app locally with SST:
Start SST in your project root.
npx sst dev
Then start your app.
Navigate to the service directory, and run your app wrapped inside
sst bind
. For instance, if you're working with an Express app:cd service
npx sst bind node app.mjsThe
sst bind
command loads service resources, environment variables, and the IAM permissions granted to the service. Read more aboutsst bind
here.Staring your app inside Docker
If you need to run the your app inside Docker locally, pass the environment variables set by
sst bind
into the docker container.sst bind "env | grep -E 'SST_|AWS_' > .env.tmp && docker run --env-file .env.tmp my-image"
This sequence fetches variables starting with SST_, saving them to the
.env.tmp
file, which is then used in the Docker run.
note
When running sst dev
, SST does not deploy your app. It's meant to be run locally.
Configuring containers
Fargate supports a variety of CPU and memory combinations for the containers. The default size used is 0.25 vCPU and 512 MB. To configure it, do the following:
new Service(stack, "MyService", {
path: "./service",
port: 3000,
cpu: "2 vCPU",
memory: "8 GB",
});
You may also configure the amount of ephemeral storage allocated to the task. The default is 20 GB. To configure it, do the following:
new Service(stack, "MyService", {
path: "./service",
port: 3000,
storage: "100 GB",
});
Auto-scaling
Your cluster can auto-scale as the traffic increases or decreases based on several metrics:
- CPU utilization (default 70%)
- Memory utilization (default 70%)
- Per-container request count (default 500)
You can also set the minimum and maximum number of containers to which the cluster can scale.
Auto-scaling is disabled by default as both the minimum and maximum are set to 1.
To configure it:
new Service(stack, "MyService", {
path: "./service",
port: 3000,
scaling: {
minContainers: 4,
maxContainers: 16,
cpuUtilization: 50,
memoryUtilization: 50,
requestsPerContainers: 1000,
}
});
Custom domains
You can configure the service with a custom domain hosted either on Route 53 or externally.
new Service(stack, "MyService", {
path: "./service",
port: 3000,
customDomain: "my-app.com",
});
Note that visitors to http://
will be redirected to https://
.
You can also configure an alias domain to point to the main domain. For instance, to set up www.my-app.com
to redirect to my-app.com
:
new Service(stack, "MyServiceSite", {
path: "./service",
port: 3000,
customDomain: {
domainName: "my-app.com",
domainAlias: "www.my-app.com",
},
});
Using AWS services
SST makes it very easy for your Service
construct to access other resources in your AWS account. If you have an S3 bucket created using the Bucket
construct, you can bind it to your app.
const bucket = new Bucket(stack, "Uploads");
new Service(stack, "MyService", {
path: "./service",
port: 3000,
bind: [bucket],
});
This will attach the necessary IAM permissions and allow your app to access the bucket via the typesafe sst/node
client.
import { Bucket } from "sst/node/bucket";
console.log(Bucket.Uploads.bucketName);
Read more about this in the Resource Binding doc.
Private services
If you don't want your service to be publicly accessible, create a private service by disabling the Application Load Balancer and CloudFront distribution.
new Service(stack, "MyService", {
path: "./service",
port: 3000,
cdk: {
applicationLoadBalancer: false,
cloudfrontDistribution: false,
},
});
Using Nixpacks
If a Dockerfile
is not found in the service's path, Nixpacks will be used to analyze the service code, and then generate a Dockerfile
within .nixpacks
. This file will build and run your application. Read more about customizing the Nixpacks builds.
note
The generated .nixpacks
directory should be added to your .gitignore
file.
Examples
Creating a Service
new Service(stack, "MyService", {
path: "./service",
port: 3000,
});
Using custom Dockerfile
new Service(stack, "MyService", {
path: "./service",
port: 3000,
file: "path/to/Dockerfile.prod",
});
Using existing ECR image
import { ContainerImage } from "aws-cdk-lib/aws-ecs";
const service = new Service(stack, "MyService", {
port: 3000,
cdk: {
container: {
image: ContainerImage.fromRegistry(
"public.ecr.aws/amazonlinux/amazonlinux:latest"
),
},
},
});
// Grants permissions to pull private images from the ECR registry
service.cdk?.taskDefinition.executionRole?.addManagedPolicy({
managedPolicyArn: "arn:aws:iam::aws:policy/AmazonECSTaskExecutionRolePolicy",
});
Configuring docker build
Here's an example of passing build args to the docker build command.
new Service(stack, "MyService", {
path: "./service",
port: 3000,
build: {
buildArgs: {
FOO: "bar"
}
}
});
Configuring log retention
The Service construct creates a CloudWatch log group to store the logs. By default, the logs are retained indefinitely. You can configure the log retention period like this:
new Service(stack, "MyService", {
path: "./service",
port: 3000,
logRetention: "one_week",
});
Configuring additional props
new Service(stack, "MyService", {
path: "./service",
port: 8080,
cpu: "2 vCPU",
memory: "8 GB",
scaling: {
minContainers: 4,
maxContainers: 16,
cpuUtilization: 50,
memoryUtilization: 50,
requestsPerContainers: 1000,
},
config: [STRIPE_KEY, API_URL],
permissions: ["ses", bucket],
});
Advanced examples
Configuring Fargate Service
Here's an example of configuring the circuit breaker for the Fargate service.
new Service(stack, "MyService", {
path: "./service",
port: 3000,
cdk: {
fargateService: {
circuitBreaker: { rollback: true },
},
},
});
Configuring Service Container
Here's an example of configuring the Fargate container health check. Make sure the curl
command exists inside the container.
import { Duration } from "aws-cdk-lib/core";
new Service(stack, "MyService", {
path: "./service",
port: 3000,
cdk: {
container: {
healthCheck: {
command: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"],
interval: Duration.minutes(30),
retries: 20,
startPeriod: Duration.minutes(30),
timeout: Duration.minutes(30),
},
},
},
});
Configuring Application Load Balancer
Here's an example of configuring the Application Load Balancer subnets.
import { SubnetType, Vpc } from "aws-cdk-lib/aws-ec2";
new Service(stack, "MyService", {
path: "./service",
port: 3000,
cdk: {
applicationLoadBalancer: {
vpcSubnets: { subnetType: SubnetType.PUBLIC }
},
vpc: Vpc.fromLookup(stack, "VPC", {
vpcId: "vpc-xxxxxxxxxx",
}),
},
});
Configuring Application Load Balancer Target
Here's an example of configuring the Application Load Balancer health check.
import { Duration } from "aws-cdk-lib/core";
new Service(stack, "MyService", {
path: "./service",
port: 3000,
cdk: {
applicationLoadBalancerTargetGroup: {
healthCheck: {
healthyHttpCodes: "200, 302",
path: "/health",
},
},
},
});
Configuring Application Load Balancer HTTP to HTTPS redirect
Here's an example of redirecting HTTP requests to HTTPS.
import { Certificate } from "aws-cdk-lib/aws-certificatemanager";
import {
ApplicationProtocol,
ListenerAction,
ListenerCertificate,
} from "aws-cdk-lib/aws-elasticloadbalancingv2";
const service = new Service(stack, "MyService", {
path: "./service",
port: 3000,
cdk: {
// Set default listener to be HTTPS
applicationLoadBalancerListener: {
protocol: ApplicationProtocol.HTTPS,
port: 443,
certificates: [ListenerCertificate.fromArn("arn:xxxxxxxxxx")],
},
},
});
// Add redirect listener
service.applicationLoadBalancer.addListener("HttpListener", {
protocol: ApplicationProtocol.HTTP,
defaultAction: ListenerAction.redirect({
protocol: "HTTPS",
host: "#{host}",
path: "/#{path}",
query: "#{query}",
port: "443",
statusCode: "HTTP_301",
}),
})
Using an existing VPC
import { Vpc } from "aws-cdk-lib/aws-ec2";
new Service(stack, "MyService", {
path: "./service",
port: 3000,
cdk: {
vpc: Vpc.fromLookup(stack, "VPC", {
vpcId: "vpc-xxxxxxxxxx",
}),
},
});
Sharing a Cluster
import { Cluster } from "aws-cdk-lib/aws-ecs";
const cluster = new Cluster(stack, "SharedCluster");
new Service(stack, "MyServiceA", {
path: "./service-a",
port: 3000,
cdk: { cluster },
});
new Service(stack, "MyServiceB", {
path: "./service-b",
port: 3000,
cdk: { cluster },
});
Constructor
new Service(scope, id, props)
Parameters
- scope Construct
- id string
- props ServiceProps
ServiceProps
architecture?
Type : "arm64" | "x86_64"
Default : "x86_64"
The CPU architecture of the container.
{
architecture: "arm64",
}
bind?
Type : Array<BindingResource>
Bind resources for the function
{
bind: [STRIPE_KEY, bucket],
}
build?
Type :
build.buildArgs?
Type : Record<string, string>
Default : No build args
Build args to pass to the docker build command.
{
build: {
buildArgs: {
FOO: "bar"
}
}
}
build.buildSsh?
Type : string
Default : No --ssh flag is passed to the build command
SSH agent socket or keys to pass to the docker build command. Docker BuildKit must be enabled to use the ssh flag
container: {
buildSsh: "default"
}
build.cacheFrom?
Type : Array<ServiceContainerCacheProps>
Default : No cache from options are passed to the build command
Cache from options to pass to the docker build command. DockerCacheOption[].
container: {
cacheFrom: [{ type: 'registry', params: { ref: 'ghcr.io/myorg/myimage:cache' }}],
}
build.cacheTo?
Type : ServiceContainerCacheProps
Default : No cache to options are passed to the build command
Cache to options to pass to the docker build command. DockerCacheOption[].
container: {
cacheTo: { type: 'registry', params: { ref: 'ghcr.io/myorg/myimage:cache', mode: 'max', compression: 'zstd' }},
}
cpu?
Type : "0.25 vCPU" | "0.5 vCPU" | "1 vCPU" | "2 vCPU" | "4 vCPU" | "8 vCPU" | "16 vCPU"
Default : "0.25 vCPU"
The amount of CPU allocated.
{
cpu: "1 vCPU",
}
customDomain?
Type : string | ServiceDomainProps
The customDomain for this service. SST supports domains that are hosted either on Route 53 or externally.
Note that you can also migrate externally hosted domains to Route 53 by following this guide.
{
customDomain: "domain.com",
}
{
customDomain: {
domainName: "domain.com",
domainAlias: "www.domain.com",
hostedZone: "domain.com"
}
}
dev?
Type :
dev.deploy?
Type : boolean
Default : false
When running
sst dev, site is not deployed. This is to ensure
sst dev` can start up quickly.
{
dev: {
deploy: true
}
}
dev.url?
Type : string
The local site URL when running
sst dev
.
{
dev: {
url: "http://localhost:3000"
}
}
environment?
Type : Record<string, string>
An object with the key being the environment variable name.
{
environment: {
API_URL: api.url,
USER_POOL_CLIENT: auth.cognitoUserPoolClient.userPoolClientId,
},
}
file?
Type : string
Default : "Dockerfile"
Path to Dockerfile relative to the defined "path".
logRetention?
Type : "one_day" | "three_days" | "five_days" | "one_week" | "two_weeks" | "one_month" | "two_months" | "three_months" | "four_months" | "five_months" | "six_months" | "one_year" | "thirteen_months" | "eighteen_months" | "two_years" | "three_years" | "five_years" | "six_years" | "seven_years" | "eight_years" | "nine_years" | "ten_years" | "infinite"
Default : Logs retained indefinitely
The duration logs are kept in CloudWatch Logs.
{
logRetention: "one_week"
}
memory?
Type : ${number} GB
Default : "0.5 GB"
The amount of memory allocated.
{
memory: "2 GB",
}
path?
Type : string
Default : "."
Path to the directory where the app is located.
permissions?
Type : Permissions
Attaches the given list of permissions to the SSR function. Configuring this property is equivalent to calling
attachPermissions()
after the site is created.
{
permissions: ["ses"]
}
port
Type : number
The port number on the container.
{
port: 8000,
}
scaling?
Type :
scaling.cpuUtilization?
Type : number
Default : 70
Scales in or out to achieve a target cpu utilization.
{
scaling: {
cpuUtilization: 50,
memoryUtilization: 50,
},
}
scaling.maxContainers?
Type : number
Default : 1
The maximum capacity for the cluster.
{
scaling: {
minContainers: 4,
maxContainers: 16,
},
}
scaling.memoryUtilization?
Type : number
Default : 70
Scales in or out to achieve a target memory utilization.
{
scaling: {
cpuUtilization: 50,
memoryUtilization: 50,
},
}
scaling.minContainers?
Type : number
Default : 1
The minimum capacity for the cluster.
{
scaling: {
minContainers: 4,
maxContainers: 16,
},
}
scaling.requestsPerContainer?
Type : number
Default : 500
Scales in or out to achieve a target request count per container.
{
scaling: {
requestsPerContainer: 1000,
},
}
storage?
Type : ${number} GB
Default : "20 GB"
The amount of ephemeral storage allocated, in GB.
{
storage: "100 GB",
}
waitForInvalidation?
Type : boolean
Default : false
While deploying, SST waits for the CloudFront cache invalidation process to finish. This ensures that the new content will be served once the deploy command finishes. However, this process can sometimes take more than 5 mins. For non-prod environments it might make sense to pass in
false
. That'll skip waiting for the cache to invalidate and speed up the deploy process.
{
waitForInvalidation: true
}
cdk?
Type :
cdk.applicationLoadBalancer?
Type : boolean | Omit<ApplicationLoadBalancerProps, "vpc">
Default : true
By default, SST creates an Application Load Balancer to distribute requests across containers. Set this to
false
to skip creating the load balancer.
{
cdk: {
applicationLoadBalancer: false
}
}
cdk.applicationLoadBalancerListener?
Type : BaseApplicationListenerProps
Customize the Application Load Balancer's target group.
{
cdk: {
applicationLoadBalancerListener: {
port: 8080
}
}
}
cdk.applicationLoadBalancerTargetGroup?
Type : ApplicationTargetGroupProps
Customize the Application Load Balancer's target group.
{
cdk: {
applicationLoadBalancerTargetGroup: {
healthCheck: {
path: "/health"
}
}
}
}
cdk.cachePolicy?
Type : ICachePolicy
By default, SST creates a CloudFront cache policy. Pass in a value to override the default policy.
import { CachePolicy } from "aws-cdk-lib/aws-cloudfront";
{
cdk: {
cachePolicy: CachePolicy.fromCachePolicyId(stack, "CachePolicy", "83da9c7e-98b4-4e11-a168-04f0df8e2c65"),
}
}
cdk.cloudfrontDistribution?
Type : boolean | ServiceCdkDistributionProps
Default : true
By default, SST creates a CloudFront distribution. Pass in a value to override the default settings this construct uses to create the CDK
Distribution
internally. Alternatively, set this to
false
to skip creating the distribution.
{
cdk: {
cloudfrontDistribution: false
}
}
cdk.cluster?
Type : ICluster
Create the service in an existing ECS cluster.
import { Cluster } from "aws-cdk-lib/aws-ecs";
{
cdk: {
cluster: Cluster.fromClusterArn(stack, "Cluster", "arn:aws:ecs:us-east-1:123456789012:cluster/my-cluster"),
}
}
cdk.container?
Type :
Customizing the container definition for the ECS task.
{
cdk: {
container: {
healthCheck: {
command: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"]
}
}
}
}
cdk.fargateService?
Type : Omit<FargateServiceProps, "cluster" | "taskDefinition">
Customize the Fargate Service.
{
cdk: {
fargateService: {
circuitBreaker: { rollback: true }
}
}
}
cdk.vpc?
Type : IVpc
Create the service in the specified VPC. Note this will only work once deployed.
import { Vpc } from "aws-cdk-lib/aws-ec2";
{
cdk: {
vpc: Vpc.fromLookup(stack, "VPC", {
vpcId: "vpc-xxxxxxxxxx",
}),
}
}
Properties
An instance of Service
has the following properties.
customDomainUrl
Type : undefined | string
If the custom domain is enabled, this is the URL of the website with the custom domain.
id
Type : string
url
Type : undefined | string
The CloudFront URL of the website.
cdk
Type : undefined |
cdk.applicationLoadBalancer
Type : undefined | ApplicationLoadBalancer
cdk.certificate
Type : undefined | ICertificate
cdk.cluster
Type : undefined | ICluster
cdk.distribution
Type : undefined | IDistribution
cdk.fargateService
Type : undefined | FargateService
cdk.hostedZone
Type : undefined | IHostedZone
cdk.taskDefinition
Type : undefined | FargateTaskDefinition
cdk.vpc
Type : undefined | IVpc
The internally created CDK resources.
Methods
An instance of Service
has the following methods.
addEnvironment
addEnvironment(name, value)
Parameters
- name string
- value string
Attaches additional environment variable to the service.
service.addEnvironment({
DEBUG: "*"
});
attachPermissions
attachPermissions(permissions)
Parameters
- permissions Permissions
Attaches the given list of permissions to allow the service to access other AWS resources.
service.attachPermissions(["sns"]);
bind
bind(constructs)
Parameters
- constructs Array<BindingResource>
Binds additional resources to service.
service.bind([STRIPE_KEY, bucket]);
ServiceDomainProps
alternateNames?
Type : Array<string>
Default : []
Specify additional names that should route to the Cloudfront Distribution. Note, certificates for these names will not be automatically generated so the
certificate
option must be specified.
domainAlias?
Type : string
Default : no alias configured
An alternative domain to be assigned to the website URL. Visitors to the alias will be redirected to the main domain. (ie.
www.domain.com
).
Use this to create a
www.
version of your domain and redirect visitors to the root domain.
domainName
Type : string
The domain to be assigned to the website URL (ie. domain.com).
Supports domains that are hosted either on Route 53 or externally.
hostedZone?
Type : string
Default : same as the
The hosted zone in Route 53 that contains the domain. By default, SST will look for a hosted zone matching the domainName that's passed in.
Set this option if SST cannot find the hosted zone in Route 53.
isExternalDomain?
Type : boolean
Default : false
Set this option if the domain is not hosted on Amazon Route 53.
cdk?
Type :
cdk.certificate?
Type : ICertificate
Import the certificate for the domain. By default, SST will create a certificate with the domain name. The certificate will be created in the
us-east-1
(N. Virginia) region as required by AWS CloudFront.
Set this option if you have an existing certificate in the
us-east-1
region in AWS Certificate Manager you want to use.
cdk.hostedZone?
Type : IHostedZone
Import the underlying Route 53 hosted zone.