We recently switched to SPA for canopas.com, which meant we had to implement SSR for SEO and loading time optimizations.
The process was not straightforward. Earlier we used Amazon S3 + Cloudflare for deployment but that does not work with SSR apps.
Today, we will see how to deploy the SSR Vite app on AWS and automate the deployment process. Though this article refers to only the Vite app, it works for almost all SSR apps. We will use Github actions but you can use any CI.
If you want to have a sneak peek, here’s a preview of our company website. It’s also open-source and the full source of the website is available on Github.
We are what we repeatedly do. Excellence, then, is not an act, but a habit. Try out Justly and start building your habits today!
Server-side rendering(SSR) is a method in which whenever we run a website page, the browser will send the request to the server, the server will process it, generate a fully rendered HTML page, and send it back to the browser.
We can create it in any frontend technology like VueJS, React, etc…
For more information visit Server-side rendering.
I’m assuming that you have created a server-side rendered app using Vuejs and Vite. Otherwise, you can refer to Vite SSR docs.
I have divided the deployment and automation process into 3 steps.
Let’s get started!
Run npm run build
command, which will run vite build
internally and create a dist
folder containing all client and server-side code.
Create a Docker file in the route directory and add the following code
FROM node:17 AS ui-build
WORKDIR /app
COPY vue-frontend/ ./
RUN npm install && npm run build
FROM node:17 AS server-build
WORKDIR /root/
COPY --from=ui-build /app/dist ./dist
COPY --from=ui-build /app/node_modules ./node_modules
COPY --from=ui-build /app/server.js /app/package*.json ./
EXPOSE 3000
CMD ["npm", "run", "serve"]
npm install
and build the app using npm run build
.dist
, server.js
, node_modules
, and package.json
from the ui-build stage to the server-build.3000
and run an app using npm run serve
.It’s time to push the docker image on ECR.
ECR image ARN
.deploy-ecr-image.sh
file in the root directory and add the following scripts.We will use $GITHUB-SHA
to separate images per commit.
IMAGE_TAG="$GITHUB_SHA"
Get the login password of ECR and login into it to push the image.
aws ecr get-login-password --region < AWS-REGION > | docker login --username AWS --password-stdin < YOUR-ECR-URI >
Build a docker image from the Dockerfile.
docker build -t < DOCKER-IMAGE-NAME >:$IMAGE_TAG .
Create a target image to push on ECR.
docker tag < DOCKER-IMAGE-NAME >:$IMAGE_TAG $IMAGE_ARN:$IMAGE_TAG
Push image on ECR
docker push $IMAGE_ARN:$IMAGE_TAG
Here is the full code of deploy-ecr-image.sh.
#! /bin/bash
set -e
IMAGE_TAG="$GITHUB_SHA"
IMAGE_ARN=$1
aws ecr get-login-password --region < AWS-REGION > | docker login --username AWS --password-stdin < YOUR-ECR-URI >
docker build -t < DOCKER-IMAGE-NAME >:$IMAGE_TAG .
docker tag < DOCKER-IMAGE-NAME >:$IMAGE_TAG $IMAGE_ARN:$IMAGE_TAG
docker push $IMAGE_ARN:$IMAGE_TAG
Create IAM ROLE
that will have permissions for our cloudformation stack resources.
Create deploy.yml
in .github/workflows
and add the following code.
- name: Checkout
uses: actions/checkout@v2.3.3
- uses: actions/setup-node@v1
with:
node-version: "17"
Configure AWS credentials
step using aws-actions/configure-aws-credentials. Use IAM-ROLE-ARN
in the role-to-assume
.- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: < IAM-ROLE-ARN >
aws-region: < AWS-REGION >
Build frontend and push on ECR
step, we have run deploy-ecr-image.sh
the file and passed ECR-IMAGE-ARN
as a parameter. This will push the docker image on ECR.- name: Build frontend and push on ECR
run: |
cd vue-frontend
sh ./deploy-ecr-image.sh < ECR-IMAGE-ARN >
Here is deploy.yml
file,
name: DeploySSRApp
on:
push:
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v2.3.3
- uses: actions/setup-node@v1
with:
node-version: "17"
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: < IAM-ROLE-ARN >
aws-region: < AWS-REGION >
- name: Build frontend and push on ECR
run: |
cd vue-frontend
sh ./deploy-ecr-image.sh < ECR-IMAGE-ARN >
In the next step, we will run that docker image using ECS.
Create template.yml
file in infrastructure
directory for creating a stack. We will add ECS
, ELB
and security groups
resources in the template.
This will add inbound rules for the application load balancer.
AWSTemplateFormatVersion: 2010-09-09
Description: An ECS, ECR, ALB and cloudfront stack
Resources:
ApplicationLBSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: SG for the Fargate ALB traffic
GroupName: < SECURITY-GROUP-NAME >
SecurityGroupIngress:
- CidrIpv6: ::/0
FromPort: 80
ToPort: 80
IpProtocol: TCP
Description: "Inbound rule for http IPv6 traffic"
- CidrIp: 0.0.0.0/0
FromPort: 80
ToPort: 80
IpProtocol: TCP
Description: "Inbound rule for http IPv4 traffic"
- CidrIpv6: ::/0
FromPort: 443
ToPort: 443
IpProtocol: TCP
Description: "Inbound rule for https IPv6 traffic"
- CidrIp: 0.0.0.0/0
FromPort: 443
ToPort: 443
IpProtocol: TCP
Description: "Inbound rule for https IPv4 traffic"
This will add inbound rules for ECS service to allow access to the load balancer.
AWSTemplateFormatVersion: 2010-09-09
Description: An ECS, ECR, ALB and cloudfront stack
Resources:
ApplicationLBToECSSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: SG for traffic between ALB and ECS service container
GroupName: < SECURITY-GROUP-NAME >
SecurityGroupIngress:
- IpProtocol: -1
SourceSecurityGroupId: !GetAtt ApplicationLBSecurityGroup.GroupId
Description: "Inbound rule for all ECS service container traffic"
Target Group will route Load balancer traffic to a specific target, which is our ECS service.
AWSTemplateFormatVersion: 2010-09-09
Description: An ECS, ECR, ALB and cloudfront stack
Resources:
TargetGroup:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
Name: < TARGET-GROUP-NAME >
VpcId: < VPC-ID >
Protocol: HTTP
Port: 3000
HealthCheckPath: /actuator/
TargetType: ip
We will create HTTP and HTTPS listeners for load balancers. HTTP listener will route traffic to HTTPS.
AWSTemplateFormatVersion: 2010-09-09
Description: An ECS, ECR, ALB and cloudfront stack
Resources:
# http (port:80) listener redirects to https
HTTPListener:
Type: "AWS::ElasticLoadBalancingV2::Listener"
Properties:
DefaultActions:
-
Order: 1
RedirectConfig:
Protocol: "HTTPS"
Port: "443"
StatusCode: "HTTP_301" #redirect from http to https
Type: "redirect"
LoadBalancerArn: !Ref ApplicationLoadBalancer #load balancer reference
Port: 80
Protocol: HTTP
# https (port:443) listener with ssl certificate
HTTPSListener:
Type: "AWS::ElasticLoadBalancingV2::Listener"
Properties:
LoadBalancerArn: !Ref ApplicationLoadBalancer #load balancer reference
Port: 443
Protocol: "HTTPS"
SslPolicy: "ELBSecurityPolicy-TLS-1-2-Ext-2018-06"
Certificates:
- CertificateArn: < CERTIFICATE-ARN > #created using AWS cerificate manager
DefaultActions:
-
Order: 1
TargetGroupArn: !Ref TargetGroup #target group reference
Type: "forward"
Application Load Balancer with HTTP and HTTPS listeners will create DNS and we can point that DNS to our domain.
The flow of running the app is like this,
Domain(https://example.com) — ELB DNS — Target Group — AWS Service
AWSTemplateFormatVersion: 2010-09-09
Description: An ECS, ECR, ALB and cloudfront stack
Resources:
ApplicationLoadBalancer:
Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
Properties:
Name: < APPLICATION_LOAD-BALANCER_NAME >
Subnets: #can add multiple subnet ids
- <SUBNET-ID-1>
- <SUBNET-ID-2>
Type: application
SecurityGroups:
- !GetAtt ApplicationLBSecurityGroup.GroupId #security group id
A cluster is used to run Task definition
using services.
AWSTemplateFormatVersion: 2010-09-09
Description: An ECS, ECR, ALB and cloudfront stack
Resources:
ECSCluster:
Type: "AWS::ECS::Cluster"
Properties:
ClusterName: < CLUSTER-NAME >
Task definition refers ECR Image that we have pushed in the second step.
AWSTemplateFormatVersion: 2010-09-09
Description: An ECS, ECR, ALB and cloudfront stack
Resources:
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
RequiresCompatibilities:
- "FARGATE"
#role for executing task definition
ExecutionRoleArn: !Sub arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole
Cpu: 256 #reserved .25vCPU
Memory: 512 #reserved .5GB memory
NetworkMode: "awsvpc"
ContainerDefinitions:
- Name: "ssr-app"
Image: < ECR-IMAGE-ARN >
MemoryReservation: 256
Memory: 512
PortMappings:
- ContainerPort: 3000 #mapping port to 3000
Protocol: tcp
Service resides in Cluster
uses Task definition
and run SSR App
AWSTemplateFormatVersion: 2010-09-09
Description: An ECS, ECR, ALB and cloudfront stack
Resources:
ECSService:
Type: AWS::ECS::Service
DependsOn:
- HTTPListener
- HTTPSListener
Properties:
LaunchType: FARGATE #launch type is farget
Cluster:
Ref: "ECSCluster" #ecs cluster reference
DesiredCount: 1
TaskDefinition:
Ref: "TaskDefinition" #task definition reference
DeploymentConfiguration:
MaximumPercent: 100
MinimumHealthyPercent: 0
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
SecurityGroups:
- !GetAtt ApplicationLBToECSSecurityGroup.GroupId #security group id
Subnets: [<SUBNET-ID-1>, <SUBNET-ID-2>] #can add multiple subnet ids
LoadBalancers:
- TargetGroupArn:
Ref: TargetGroup #target group reference
ContainerPort: 3000
ContainerName: < ECR-CONTAINER-NAME>
Now we have all the resources of the cloudformation stack in template.yml
, We have to execute this template from the deploy.yml
file.
Add the following step in deploy.yml
- name: Deploy cloudformation stack
id: SSR-stack
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
name: SSR-stack
template: infrastructure/template.yml
capabilities: CAPABILITY_IAM,CAPABILITY_NAMED_IAM
timeout-in-minutes: "10"
no-fail-on-empty-changeset: "1"
Yes, that’s it!
Now whenever we push code on Github, the deploy.yml
will run and deploy your app on AWS.
That’s it for today. Hope you have an idea of how SSR app deployment works. This is not the only way to deploy it though. AWS offers so many tools that can handle this type of deployment. However, at least you have a starting point if you need to deploy SSR apps.
We’re Grateful to have you with us on this journey!
Suggestions and feedback are more than welcome!
Please reach us at Canopas Twitter handle @canopas_eng with your content or feedback. Your input enriches our content and fuels our motivation to create more valuable and informative articles for you.
Get started today
Let's build the next
big thing!
Let's improve your business's digital strategy and implement robust mobile apps to achieve your business objectives. Schedule Your Free Consultation Now.
Get Free ConsultationGet started today
Let's build the next big thing!
Let's improve your business's digital strategy and implement robust mobile apps to achieve your business objectives. Schedule Your Free Consultation Now.
Get Free Consultation