Deploy Deno to Amazon Lightsail
Amazon Lightsail is the easiest and cheapest way to get started with Amazon Web Services. It allows you to host virtual machines and even entire container services.
This How To guide will show you how to deploy a Deno app to Amazon Lightsail using Docker, Docker Hub, and GitHub Actions.
Before continuing, make sure you have:
Create Dockerfile and docker-compose.yml
To focus on the deployment, our app will simply be a main.ts
file that returns
a string as an HTTP response:
import { Application } from "https://deno.land/x/oak/mod.ts";
const app = new Application();
app.use((ctx) => {
ctx.response.body = "Hello from Deno and AWS Lightsail!";
});
await app.listen({ port: 8000 });
Then, we’ll create two files – Dockerfile
and docker-compose.yml
– to
build the Docker image.
In our Dockerfile
, let’s add:
FROM denoland/deno
EXPOSE 8000
WORKDIR /app
ADD . /app
RUN deno cache main.ts
CMD ["run", "--allow-net", "main.ts"]
Then, in our docker-compose.yml
:
version: '3'
services:
web:
build: .
container_name: deno-container
image: deno-image
ports:
- "8000:8000"
Let’s test this locally by running docker compose -f docker-compose.yml build
,
then docker compose up
, and going to localhost:8000
.
It works!
Build, Tag, and Push to Docker Hub
First, let’s sign into Docker Hub and
create a repository. Let’s name it deno-on-aws-lightsail
.
Then, let’s tag and push our new image, replacing username
with yours:
Then, let’s build the image locally. Note our docker-compose.yml
file will
name the build deno-image
.
docker compose -f docker-compose.yml build
Let’s tag the local
image with {{ username }}/deno-on-aws-lightsail
:
docker tag deno-image {{ username }}/deno-on-aws-lightsail
We can now push the image to Docker Hub:
docker push {{ username }}/deno-on-aws-lightsail
After that succeeds, you should be able to see the new image on your Docker Hub repository:
Create and Deploy to a Lightsail Container
Let’s head over to the Amazon Lightsail console.
Then click “Containers” and “Create container service”. Half way down the page, click “Setup your first Deployment” and select “Specify a custom deployment”.
You can write whatever container name you’d like.
In Image
, be sure to use {{ username }}/{{ image }}
that you have set in
your Docker Hub. For this example, it is lambtron/deno-on-aws-lightsail
.
Let’s click Add open ports
and add 8000
.
Finally, under PUBLIC ENDPOINT
, select the container name that you just
created.
The full form should look like below:
When you’re ready, click “Create container service”.
After a few moments, your new container should be deployed. Click on the public address and you should see your Deno app:
Automate using GitHub Actions
In order to automate that process, we’ll use the aws
CLI with the
lightsail
subcommand.
The steps in our GitHub Actions workflow will be:
- Checkout the repo
- Build our app as a Docker image locally
- Install and authenticate AWS CLI
- Push local Docker image to AWS Lightsail Container Service via CLI
Pre-requisites for this GitHub Action workflow to work:
- an AWS Lightsail Container Instance is created (see section above)
- IAM user and relevant permissions set. (See here to learn more about managing access to Amazon Lightsail for an IAM user.)
AWS_ACCESS_KEY_ID
andAWS_SUCCESS_ACCESS_KEY
for your permissioned user. (Follow this AWS guide to get generate anAWS_ACCESS_KEY_ID
andAWS_SUCCESS_ACCESS_KEY
.)
Let’s create a new file container.template.json
, which contains configuration
for how to make the service container deployment. Note the similarities these
option values have with the inputs we entered manually in the previous section.
{
"containers": {
"app": {
"image": "",
"environment": {
"APP_ENV": "release"
},
"ports": {
"8000": "HTTP"
}
}
},
"publicEndpoint": {
"containerName": "app",
"containerPort": 8000,
"healthCheck": {
"healthyThreshold": 2,
"unhealthyThreshold": 2,
"timeoutSeconds": 5,
"intervalSeconds": 10,
"path": "/",
"successCodes": "200-499"
}
}
}
Let’s add the below to your .github/workflows/deploy.yml
file:
name: Build and Deploy to AWS Lightsail
on:
push:
branches:
- main
env:
AWS_REGION: us-west-2
AWS_LIGHTSAIL_SERVICE_NAME: container-service-2
jobs:
build_and_deploy:
name: Build and Deploy
runs-on: ubuntu-latest
steps:
- name: Checkout main
uses: actions/checkout@v2
- name: Install Utilities
run: |
sudo apt-get update
sudo apt-get install -y jq unzip
- name: Install AWS Client
run: |
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install || true
aws --version
curl "https://s3.us-west-2.amazonaws.com/lightsailctl/latest/linux-amd64/lightsailctl" -o "lightsailctl"
sudo mv "lightsailctl" "/usr/local/bin/lightsailctl"
sudo chmod +x /usr/local/bin/lightsailctl
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ${{ env.AWS_REGION }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Build Docker Image
run: docker build -t ${{ env.AWS_LIGHTSAIL_SERVICE_NAME }}:release .
- name: Push and Deploy
run: |
service_name=${{ env.AWS_LIGHTSAIL_SERVICE_NAME }}
aws lightsail push-container-image \
--region ${{ env.AWS_REGION }} \
--service-name ${service_name} \
--label ${service_name} \
--image ${service_name}:release
aws lightsail get-container-images --service-name ${service_name} | jq --raw-output ".containerImages[0].image" > image.txt
jq --arg image <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>c</mi><mi>a</mi><mi>t</mi><mi>i</mi><mi>m</mi><mi>a</mi><mi>g</mi><mi>e</mi><mi mathvariant="normal">.</mi><mi>t</mi><mi>x</mi><mi>t</mi><msup><mo stretchy="false">)</mo><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mi mathvariant="normal">.</mi><mi>c</mi><mi>o</mi><mi>n</mi><mi>t</mi><mi>a</mi><mi>i</mi><mi>n</mi><mi>e</mi><mi>r</mi><mi>s</mi><mi mathvariant="normal">.</mi><mi>a</mi><mi>p</mi><mi>p</mi><mi mathvariant="normal">.</mi><mi>i</mi><mi>m</mi><mi>a</mi><mi>g</mi><mi>e</mi><mo>=</mo></mrow><annotation encoding="application/x-tex">(cat image.txt) '.containers.app.image = </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.0019em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal">c</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">ima</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal">e</span><span class="mord">.</span><span class="mord mathnormal">t</span><span class="mord mathnormal">x</span><span class="mord mathnormal">t</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mord">.</span><span class="mord mathnormal">co</span><span class="mord mathnormal">n</span><span class="mord mathnormal">t</span><span class="mord mathnormal">ain</span><span class="mord mathnormal">ers</span><span class="mord">.</span><span class="mord mathnormal">a</span><span class="mord mathnormal">pp</span><span class="mord">.</span><span class="mord mathnormal">ima</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span></span></span></span>image' container.template.json > container.json
aws lightsail create-container-service-deployment --service-name <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>s</mi><mi>e</mi><mi>r</mi><mi>v</mi><mi>i</mi><mi>c</mi><msub><mi>e</mi><mi>n</mi></msub><mi>a</mi><mi>m</mi><mi>e</mi></mrow><mo>−</mo><mo>−</mo><mi>c</mi><mi>l</mi><mi>i</mi><mo>−</mo><mi>i</mi><mi>n</mi><mi>p</mi><mi>u</mi><mi>t</mi><mo>−</mo><mi>j</mi><mi>s</mi><mi>o</mi><mi>n</mi><mi>f</mi><mi>i</mi><mi>l</mi><mi>e</mi><mo>:</mo><mi mathvariant="normal">/</mi><mi mathvariant="normal">/</mi></mrow><annotation encoding="application/x-tex">{service_name} --cli-input-json file://</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8095em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.02778em;">ser</span><span class="mord mathnormal" style="margin-right:0.03588em;">v</span><span class="mord mathnormal">i</span><span class="mord mathnormal">c</span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal">am</span><span class="mord mathnormal">e</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord">−</span><span class="mord mathnormal">c</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">i</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.854em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">in</span><span class="mord mathnormal">p</span><span class="mord mathnormal">u</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.05724em;">j</span><span class="mord mathnormal">so</span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mord mathnormal">i</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">//</span></span></span></span>(pwd)/container.json
Whoa there is a lot going on here! The last two steps are most important:
Build Docker Image
and Push and Deploy
.
docker build -t ${{ env.AWS_LIGHTSAIL_SERVICE_NAME }}:release .
This command builds our Docker image with the name container-service-2
and
tags it release
.
aws lightsail push-container-image ...
This command pushes the local image to our Lightsail container.
aws lightsail get-container-images --service-name ${service_name} | jq --raw-output ".containerImages[0].image" > image.txt
This command retrieves the image information and, using
jq
, parses it and saves the image name in a
local file image.txt
.
jq --arg image <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>c</mi><mi>a</mi><mi>t</mi><mi>i</mi><mi>m</mi><mi>a</mi><mi>g</mi><mi>e</mi><mi mathvariant="normal">.</mi><mi>t</mi><mi>x</mi><mi>t</mi><msup><mo stretchy="false">)</mo><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mi mathvariant="normal">.</mi><mi>c</mi><mi>o</mi><mi>n</mi><mi>t</mi><mi>a</mi><mi>i</mi><mi>n</mi><mi>e</mi><mi>r</mi><mi>s</mi><mi mathvariant="normal">.</mi><mi>a</mi><mi>p</mi><mi>p</mi><mi mathvariant="normal">.</mi><mi>i</mi><mi>m</mi><mi>a</mi><mi>g</mi><mi>e</mi><mo>=</mo></mrow><annotation encoding="application/x-tex">(cat image.txt) '.containers.app.image = </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.0019em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal">c</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">ima</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal">e</span><span class="mord">.</span><span class="mord mathnormal">t</span><span class="mord mathnormal">x</span><span class="mord mathnormal">t</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mord">.</span><span class="mord mathnormal">co</span><span class="mord mathnormal">n</span><span class="mord mathnormal">t</span><span class="mord mathnormal">ain</span><span class="mord mathnormal">ers</span><span class="mord">.</span><span class="mord mathnormal">a</span><span class="mord mathnormal">pp</span><span class="mord">.</span><span class="mord mathnormal">ima</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span></span></span></span>image' container.template.json > container.json
This command uses the image name saved in image.txt
and
container.template.json
and creates a new options file called
container.json
. This options file will be passed to aws lightsail
for the
final deployment in the next step.
aws lightsail create-container-service-deployment --service-name <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>s</mi><mi>e</mi><mi>r</mi><mi>v</mi><mi>i</mi><mi>c</mi><msub><mi>e</mi><mi>n</mi></msub><mi>a</mi><mi>m</mi><mi>e</mi></mrow><mo>−</mo><mo>−</mo><mi>c</mi><mi>l</mi><mi>i</mi><mo>−</mo><mi>i</mi><mi>n</mi><mi>p</mi><mi>u</mi><mi>t</mi><mo>−</mo><mi>j</mi><mi>s</mi><mi>o</mi><mi>n</mi><mi>f</mi><mi>i</mi><mi>l</mi><mi>e</mi><mo>:</mo><mi mathvariant="normal">/</mi><mi mathvariant="normal">/</mi></mrow><annotation encoding="application/x-tex">{service_name} --cli-input-json file://</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8095em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.02778em;">ser</span><span class="mord mathnormal" style="margin-right:0.03588em;">v</span><span class="mord mathnormal">i</span><span class="mord mathnormal">c</span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal">am</span><span class="mord mathnormal">e</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord">−</span><span class="mord mathnormal">c</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">i</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.854em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">in</span><span class="mord mathnormal">p</span><span class="mord mathnormal">u</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.05724em;">j</span><span class="mord mathnormal">so</span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mord mathnormal">i</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">//</span></span></span></span>(pwd)/container.json
Finally, this command creates a new deployment using the service_name
, along
with the config settings in container.json
.
When you push to GitHub and the Action succeeds, you’ll be able to see your new Deno app on AWS: