AWS has introduced Lightsail (https://aws NULL.amazon NULL.com/lightsail/) to compete with Digital Ocean, Linode, etc. for an inexpensive VPS (Virtual Private Server) offering.

In this article we will use Terraform (https://www NULL.terraform NULL.io/) (Infrastructure as Code) to swiftly bring up an AWS Lightsail instance in us-east-1 on a static IP, add a DNS Zone for the site in mention and install docker/docker-compose on it.

We will use ‘myweb’ as an example in this article, using the same base path of ‘dev’ that was previously created, the container-admin group and using ~/.local/bin for the binary.

Please use ‘Get started with Lightsail for free’ (https://portal NULL.aws NULL.amazon NULL.com/billing/signup?client=lightsail&fid=1A3F6B376ECAC516-2C15C39C5ACECACB&redirect_url=https%3A%2F%2Flightsail NULL.aws NULL.amazon NULL.com%2Fls%2Fsignup#/start) prior to commencing with this article.

–>
Go in to the dev directory/link located within your home directory:

$ cd ~/dev

Upgrade the AWS CLI on your host:

$ pip3 install awscli --upgrade --user && chmod 754 ~/.local/bin/aws

Grab Terraform:

$ wget https://releases.hashicorp.com/terraform/0.12.9/terraform_0.12.9_linux_amd64.zip

Install Unzip if you do not have it installed:

$ sudo apt -y install unzip

Unzip it to ~/.local/bin and set permissions accordingly on it:

$ unzip terraform_0.12.9_linux_amd64.zip -d ~/.local/bin && chmod 754 ~/.local/bin/terraform

Create a work folder and change in to it:

$ mkdir -p terraform/myweb/scripts && cd terraform/myweb

Add an IAM Policy to the container-admin group so it will have access to the Lightsail API:
AWS UI Console -> Services -> Security, Identity, & Compliance -> IAM -> Policies -> Create Policy -> JSON (replace <AWS ACCOUNT ID> in the Resource arn with your Account’s ID (shown under the top right drop-down (of your name) within the My Account page next to the Account Id: under Account Settings)):

{
     "Version": "2012-10-17",
     "Statement": [
         {
             "Effect": "Allow",
             "Action": [
                 "lightsail:GetRelationalDatabaseEvents",
                 "lightsail:GetActiveNames",
                 "lightsail:GetOperations",
                 "lightsail:GetBlueprints",
                 "lightsail:GetRelationalDatabaseMasterUserPassword",
                 "lightsail:ExportSnapshot",
                 "lightsail:UnpeerVpc",
                 "lightsail:GetRelationalDatabaseLogEvents",
                 "lightsail:GetRelationalDatabaseBlueprints",
                 "lightsail:GetRelationalDatabaseBundles",
                 "lightsail:CopySnapshot",
                 "lightsail:GetRelationalDatabaseMetricData",
                 "lightsail:PeerVpc",
                 "lightsail:IsVpcPeered",
                 "lightsail:UpdateRelationalDatabaseParameters",
                 "lightsail:GetRegions",
                 "lightsail:GetOperation",
                 "lightsail:GetDisks",
                 "lightsail:GetRelationalDatabaseParameters",
                 "lightsail:GetBundles",
                 "lightsail:GetRelationalDatabaseLogStreams",
                 "lightsail:CreateKeyPair",
                 "lightsail:ImportKeyPair",
                 "lightsail:DeleteKeyPair",
                 "lightsail:GetInstance",
                 "lightsail:CreateInstances",
                 "lightsail:DeleteInstance",
                 "lightsail:GetDomains",
                 "lightsail:GetDomain",
                 "lightsail:CreateDomain",
                 "lightsail:DeleteDomain",
                 "lightsail:GetStaticIp",
                 "lightsail:AllocateStaticIp",
                 "lightsail:AttachStaticIp",
                 "lightsail:DetachStaticIp",
                 "lightsail:ReleaseStaticIp"
             ],
             "Resource": "*"         
         },
         {
             "Effect": "Allow",
             "Action": "lightsail:",
             "Resource": [
                 "arn:aws:lightsail::<AWS ACCOUNT ID>:StaticIp/*",
                 "arn:aws:lightsail::<AWS ACCOUNT ID>:ExportSnapshotRecord/*",
                 "arn:aws:lightsail::<AWS ACCOUNT ID>:Instance/*",
                 "arn:aws:lightsail::<AWS ACCOUNT ID>:CloudFormationStackRecord/*",
                 "arn:aws:lightsail::<AWS ACCOUNT ID>:RelationalDatabaseSnapshot/*",
                 "arn:aws:lightsail::<AWS ACCOUNT ID>:RelationalDatabase/*",
                 "arn:aws:lightsail::<AWS ACCOUNT ID>:InstanceSnapshot/*",
                 "arn:aws:lightsail::<AWS ACCOUNT ID>:Domain/*",
                 "arn:aws:lightsail::<AWS ACCOUNT ID>:LoadBalancer/*",
                 "arn:aws:lightsail::<AWS ACCOUNT ID>:KeyPair/*",
                 "arn:aws:lightsail::<AWS ACCOUNT ID>:Disk/*"
             ]
         }
     ]
 }

Review Policy ->

Name: AllowLightsail
Description: Allow access to Lightsail.

Create Policy.

Groups -> container-admin -> Attach Policy -> Search for AllowLightsail -> Attach Policy.

Generate an SSH Key Pair (no password) and restrict permissions on it:

$ ssh-keygen -q -t rsa -b 2048 -N '' -f ~/.ssh/myweb && chmod 400 ~/.ssh/myweb

Import the public key to Lightsail:

$ aws lightsail import-key-pair --key-pair-name myweb --public-key-base64 file://~/.ssh/myweb.pub

Set the version to greater then or equal to 2.0, interpolate the region and use the AWS CLI credentials file:

$ cat << 'EOF' > provider.tf
> provider "aws" {
>         version                         = ">= 2.0"
>
>         region                          = "${var.region}"
>         shared_credentials_file         = "~/.aws/credentials"
> }
> EOF

Set the default region as a variable:

$ cat << 'EOF' > vars.tf
> variable "region" {
>         default = "us-east-1"
> }
> EOF

Create a Lightsail DNS Zone of myweb.com, allocate a static IP for it, create a nano instance based off of Ubuntu 18_04, reference an extraneous file for user_data (run once script on Virtual Machine boot), tag it and attach the allocated Static IP to it:

$ cat << 'EOF' > lightsail.tf
> resource "aws_lightsail_domain" "myweb" {
>         domain_name = "myweb.com"
> }
>
> resource "aws_lightsail_static_ip" "myweb" {
>         name = "static-ip_myweb"
> }
>
> resource "aws_lightsail_instance" "myweb" {
>         name                    = "site_myweb"
>         availability_zone       = "${var.region}a"
>         blueprint_id            = "ubuntu_18_04"
>         bundle_id               = "nano_2_0"
>         key_pair_name           = "myweb"
>         user_data               = "${data.template_file.init_script.rendered}"
>         tags    = {
>                Site = "myweb.com"
>         }
> }
>
> resource "aws_lightsail_static_ip_attachment" "myweb" {
>        static_ip_name = "${aws_lightsail_static_ip.myweb.name}"
>        instance_name  = "${aws_lightsail_instance.myweb.name}"
> }
> EOF

Output our allocated and attached static IP after creation:

$ cat << 'EOF' > output.tf
> output "static_public_ip" {
>        value = "${aws_lightsail_static_ip.myweb.ip_address}"
> }
> EOF

Create a template file to reference a run-once on boot user_data script:

$ cat << 'EOF' > install.tf
> data "template_file" "init_script" {
>        template = "${file("scripts/install.sh")}"
> }
> EOF

Create the shell script for user_data:

$ cat << 'EOF' > scripts/install.sh
> #!/bin/bash
>
> MY_HOME="/home/ubuntu"
> export DEBIAN_FRONTEND=noninteractive
>
> # Install prereqs
> apt update
> apt install -y python3-pip apt-transport-https ca-certificates curl software-properties-common
> # Install docker
> curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
> add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
> apt update
> apt install -y docker-ce
> # Install docker-compose
> su ubuntu -c "mkdir -p $MY_HOME/.local/bin"
> su ubuntu -c "pip3 install docker-compose --upgrade --user && chmod 754 $MY_HOME/.local/bin/docker-compose"
> usermod -aG docker ubuntu
> # Add PATH
> printf "\nexport PATH=\$PATH:$MY_HOME/.local/bin\n" >> $MY_HOME/.bashrc
>
> exit 0
> EOF

Initialize the directory:

$ terraform init

Run a dry-run to see what will occur:

$ terraform plan

Provision:

$ terraform apply -auto-approve

Log on to the instance (up to ~30 seconds may be needed for the attachment of the static IP to the instance):

$ ssh -i ~/.ssh/myweb ubuntu@<The value of static_public_ip that was reported.  One can also use 'terraform output static_public_ip' to print it again.>

Type yes and hit enter to accept.

On the host (a short while is needed for the run-once script to complete):

$ docker --version
$ docker-compose --version
$ logout

Tear down what was created by first performing a dry-run to see what will occur:

$ terraform plan -destroy 

Tear down the instance:

$ terraform destroy -auto-approve

<–

References:

«