AWS has introduced Lightsail to compete with Digital Ocean, Linode, etc. for an inexpensive VPS (Virtual Private Server) offering.

In this article we will use Terraform (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’ 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 update && 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"
>   profile                 = "default"
> }
> 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 micro 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               = "micro_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:

Source:
terraform_aws_myweb