SpaceRocket |> Blog

Think, Learn, Share

Michael Chavez
Michael Chavez

Deploying Elixir Phoenix App to AWS

Posted by Michael Chavez on August 24, 2019

Prerequisites

  • Comfortable with command line
  • Navigating directions that might have become outdated

Table of Contents

  1. Create an Amazon VPC
  2. Create Additional Subnets
  3. Create a VPC Security Group for a Public Web Server
  4. Create a VPC Security Group for a Private DB Instance
  5. Create a DB Subnet Group
  6. Creating an IAM role
  7. Create RDS Postgres Database
  8. Create an Instance
  9. Create an Elastic IP
  10. Point Domain Name to Server
  11. Log Into Instance
  12. Adding ssh config
  13. Install Build Essentials
  14. Install Erlang
  15. Install Elixir
  16. Install Phoenix
  17. Install Node.js
  18. Install Git
  19. Install Nginx
  20. Nginx Server Blocks
  21. Add SSL
  22. Prepare Server Block for Phoenix App
  23. Preparing Phoenix App for Production
  24. Create Github Token and Repo

Create an Amazon VPC

  1. Open the Amazon VPC console at https://console.aws.amazon.com/vpc/
  2. In the top-right corner of the AWS Management Console, choose the region to create your VPC in. This example uses the N. California region.
  3. Click Launch VPC Wizard and click Select Settings:
    IPv4 CIDR block: 10.0.0.0/16
    IPv6 CIDR block: No IPv6 CIDR Block
    VPC name: space-rocket-vpc
    Public subnet’s IPv4 CIDR: 10.0.0.0/24
    Availability Zone: us-west-1c
    Public subnet name: Space-Rocket Public
    Service endpoints: Skip this field.
    Enable DNS hostnames: Yes
    Hardware tenancy: Default

Create Additional Subnets

  1. Go to VPC > Subnets > Create Subnet. Fill in with these settings:
    Name tag: Space-Rocket Private 1
    VPC: space-rocket-vpc
    Availability Zone: us-west-1a (Choose an Availability Zone that is different from the one that you chose for the first private subnet.)
    IPv4 CIDR block: 10.0.2.0/24

Create a VPC Security Group for a Public Web Server

  1. Go to VPC Dashboard > Security > Security Groups > Create security group with these Security Group settings:
    Security group name: SpaceRocketSecurityGroup
    Description: Space-Rocket Security Group
    VPC: space-rocket-vpc
  2. Create and make note of Security Group ID: ex: sg-02e40a02f8f297d88
  3. Get your IP address from https://checkip.amazonaws.com
  4. Under the list of security groups, choose the Inbound Rules tab, and then choose Edit rules and then Add Rule with these settings:
    Type: SSH
    Source: The IP address or range from https://checkip.amazonaws.com, for example: 203.0.113.25/32.
  5. Choose Add Rule again, this time with these settings: Type: HTTP
    Source: 0.0.0.0/0, ::/0

Create a VPC Security Group for a Private DB Instance

  1. Go to VPC Dashboard > Security > Security Groups > Create security group
  2. Fill in with these settings:
    Security group name: SpaceRocketDBSecurityGroup
    Description: Space-Rocket DB Security Group
    VPC: vpc-identifier (10.0.0.0/16) | space-rocket-vpc
  3. Click create
  4. Select the security group we just created, under the list of security groups, choose the Inbound Rules tab, and then choose Edit rules and then Add Rule with these settings:
    Type: PostgreSQL
    Source: The identifier of the SpaceRocketSecurityGroup security group that you created previously, for example: sg-02e40a02f8f297d99.

Create a DB Subnet Group

  1. Go to RDS Dashboard > Subnet groups > Create Subnet Group and fill in with these settings:
    Name: SpaceRocketDBSubnetGroup
    Description: Space-Rocket DB Subnet Group
    VPC: space-rocket-vpc
  2. In the Add subnets section, choose Add all the subnets related to this VPC.
  3. Choose Create.

Creating an IAM role

  1. Navigate to the IAM dashboard
  2. Create group named CodeDeployGroup with AmazonS3FullAccess and AWSCodeDeployFullAccess permissions and pragmatic access
  3. Create a user named CodeDeployUser
  4. Make a note of the user’s Access Key and Secret Access Key
  5. Create a role with AWS service as the type of trusted entity Choose EC2 as the service that will use the role
  6. Click next
  7. Attach AWSCodeDeployRole and AmazonEC2FullAccess permission policies to the role
  8. Name the role AWSCodeDeployRole
  9. Edit the trust relationship to include codeDeploy service in your region:
{
  "Version": "2012-10-17",
  "Statement": [
  {
      "Effect": "Allow",
      "Principal": {
        "Service": [
            "ec2.amazonaws.com",
            "codedeploy.us-west-1.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Create RDS Postgres Database

  1. Go to **RDS Dashboard > Create Database
  2. Choose these settings:
    Easy Create: disabled
    Engine Option: PostgreSQL
    Version: Choose default
    Template: Free tier
    DB instance identifier: space-rocket-db
    Master username: postgres
    Master password: postgres
    DB instance size: db.t2.micro
    Connectivity: space-rocket-vpc
    VPC Security group: SpaceRocketSecurityGroup, SpaceRocketDBSecurityGroup
    Availability zone: us-west-1c
    Initial database name: space_rocket_prod
  3. Click create

Create an Instance

  1. Go to services > EC2
  2. Click Launch Instance button
  3. Select AWS Marketplace, Debian GNU/Linux 9 (Stretch)
  4. Select t2.micro
  5. Click Next: Configure Instance Details Network: Choose the VPC that you created earlier, for example: vpc-identifier (10.0.0.0/16) | space-rocket-vpc
    Subnet: Choose the Private Subnet that you created earlier, for example: vpc-identifier (10.0.0.0/16) | Space-Rocket Public
    Auto-assign Public IP: Use subnet setting (Disable)
    IAM role: AWSCodeDeployRole (created in previous step)
  6. Leave storage settings as default
  7. Add Tags: key: Name, value: Code Deploy Instanceaba
  8. Configure Security Group, select SpaceRocketDBSecurityGroup and SpaceRocketSecurityGroup
  9. Click Launch
  10. Select your keypair or create one if you haven’t already

Create an Elastic IP

  1. Go to EC2 Dashboard > Network & Security > Elastic IPs > Allocate
  2. Select the Elastic IP you just created and select Associate address with these settings:
    Resource type: Instance
    Instance: The instance you just created
  3. Click Associate and make note of IP address

Point Domain Name to Server

  1. Log into domain register and locate the domain you want to point to AWS
  2. Create or modify record with @ symbol, set the type to “A Record” and set value to the Elastic IP
  3. Locate entry with “www”, set type to “CNAME Record” and set value to public DNS of the Elastic IP, example: ec2-52-33-201-154.us-west-1.compute.amazonaws.com.
  4. Go to Services>Route 53
  5. Click Create Hosted Zone button
  6. Enter your domain name
  7. Create recordset with value www and value of the Elastic IP address

Log Into Instance

  1. After Instance State is set to Running, Log into your instance by getting the command from clicking connect(Note that you have to change root to admin if using Debian):
    ssh -i ~/.ssh/yourkey.pem admin@ec2-54-219-160-65.us-west-1.compute.amazonaws.com

Adding ssh config

  1. While logged into instance, open up ~/.ssh/config and contents like this:
~/.ssh/config
Host space-rocket.dev
     HostName ec2-54-219-160-65.us-west-1.compute.amazonaws.com
     User admin
     IdentityFile ~/.ssh/YourPemKey.pem

Not working? Don’t forget to reopen your shell and check the inbound rules on your EC2 instance console

Install Build Essentials

  1. sudo apt-get update
  2. sudo apt-get install build-essential

Install ImageMagick

  1. wget https://www.imagemagick.org/download/ImageMagick.tar.gz
  2. tar xvzf ImageMagick.tar.gz
  3. cd ImageMagick-7.0.8-61/
  4. ./configure '--with-png=yes' '--with-jpeg=yes'
  5. make
  6. sudo make install
  7. sudo ldconfig /usr/local/lib
  8. See if jpeg installed:identify -list format

or sudo apt-get install imagemagick

Install Erlang

  1. wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i erlang-solutions_1.0_all.deb
  2. sudo apt-get update
  3. sudo apt-get install esl-erlang

Install Elixir

  1. sudo apt-get install elixir
  2. elixir -v
  3. mix local.hex

Install Phoenix

  1. mix local.hex
  2. mix archive.install hex phx_new 1.4.9 or latest according to documentation here

Install Node.js

  1. curl -sL https://deb.nodesource.com/setup_12.x | sudo bash -

Install Git

  1. sudo apt update
  2. sudo apt install git
  3. git --version

Install Nginx

  1. sudo apt update
  2. sudo apt install nginx
  3. systemctl status nginx
  4. Go to your Elastic IP address and you should see the “Welcome to nginx!” page

Nginx Server Blocks

  1. sudo mkdir -p /var/apps/space-rocket.dev/app
  2. sudo chown -R $USER:$USER /var/apps/space-rocket.dev/app
  3. sudo chmod -R 755 /var/apps/space-rocket.dev
/var/apps/space-rocket.dev/app/index.html
<html>
    <head>
        <title>Welcome to Space-Rocket.dev!</title>
    </head>
    <body>
        <h1>Success!  The space-rocket.dev server block is working!</h1>
    </body>
</html>
  1. sudo nano /etc/nginx/sites-available/space-rocket.dev
/etc/nginx/sites-available/space-rocket.dev
server {
    listen 80;
    listen [::]:80;

    root /var/apps/space-rocket.dev/app;
    index index.html index.htm index.nginx-debian.html;

    server_name space-rocket.dev www.space-rocket.dev;

    location / {
        try_files $uri $uri/ =404;
    }
}
  1. sudo ln -s /etc/nginx/sites-available/space-rocket.dev /etc/nginx/sites-enabled/
  2. sudo nano /etc/nginx/nginx.conf
    Modify to look like:
/etc/nginx/nginx.conf

...

http {
    ...
    server_names_hash_bucket_size 64;
    ...
}

...
  1. sudo nginx -t
  2. sudo systemctl restart nginx

Add SSL

  1. sudo nano /etc/apt/sources.list
/etc/apt/sources.list
...
deb http://deb.debian.org/debian stretch-backports main contrib non-free
deb-src http://deb.debian.org/debian stretch-backports main contrib non-free
  1. sudo apt update
  2. sudo apt install python-certbot-nginx -t stretch-backportsba
  3. sudo certbot --nginx -d space-rocket.dev -d www.space-rocket.dev
  4. Choose “HTTPS redirect” option when asked
  5. Check it out at space-rocket.dev

Prepare Server Block for Phoenix App

/etc/nginx/sites-available/space-rocket.dev
upstream phoenix {
    server 127.0.0.1:8080;
}

server {
    listen 80;
    listen [::]:80;

    root /var/apps/space-rocket.dev/app;
    index index.html index.htm index.nginx-debian.html;

    server_name space-rocket.dev www.space-rocket.dev;

    location / {
        allow all;

        # Proxy Headers
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-Cluster-Client-Ip $remote_addr;

        # WebSockets
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_pass http://phoenix;
    }
}

Preparing Phoenix App for Production

Update prod config: sudo nano config/prod.exs

config/prod.exs
...
config :space_rocket, SpaceRocketWeb.Endpoint,
  server: true,
  url: [host: "space-rocket.dev", port: 80],
  cache_static_manifest: "priv/static/cache_manifest.json"
...

Create Github Token and Repo

  1. Log into your Github account
  2. In the top right corner click your profile photo and click Settings>Developer settings>Personal access token>Generate a personal access token in the drop-down
  3. Name the token CodeDeployToken
  4. Select repo, write:packages, read:packages and admin:repo_hook
  5. Make a note of your token and store in a safe and secure place
  6. git clone https://User:<github-token>@github.com/User/UserRepo

Create .env file

  1. Create a .env file: nano .env
.env
#!/bin/bash
export SECRET_KEY_BASE="$(mix phx.gen.secret)"
export GUARDIAN_GEN_SECRET="$(mix guardian.gen.secret)"
export DATABASE_URL="ecto://postgres:postgres@space-rocket-db-2.blahblah.us-west-1.rds.amazonaws.com:5432/some_db"
export PORT="8080"
export MIX_ENV=prod
export S3_STORAGE="Arc.Storage.S3"
export S3_BUCKET="spacerocketphx2"
export AWS_ACCESS_KEY_ID="yourawsaccesskey"
export AWS_SECRET_ACCESS_KEY="yoursecretaccesskey"
export AWS_REGION="us-west-1"
export S3_HOST="s3.us-west-1.amazonaws.com"
export S3_REGION="us-west-1"
export SES_REGION="us-west-2"
export POOL_SIZE="2"
  1. Add .env to .gitignore: echo .env >> .gitignore
  2. source ./.env
  3. mix deps.get
  4. mix local.rebar --force
  5. echo $SECRET_KEY_BASE

Deploying

  1. mix deps.get
  2. mix ecto.create
  3. mix deps.get --only prod
  4. MIX_ENV=prod mix compile
  5. npm run deploy --prefix ./assets
  6. mix phx.digest
  7. MIX_ENV=prod mix ecto.migrate
  8. PORT=8080 MIX_ENV=prod mix phx.server
  9. Visit site

Make a release

  1. mix deps.get --only prod
  2. MIX_ENV=prod mix compile
  3. npm run deploy --prefix ./assets
  4. mix phx.digest
  5. MIX_ENV=prod mix release

Release Bash File

  1. Create a release.sh file with the these contents:
release.sh
#!/bin/bash
mix deps.get --only prod
MIX_ENV=prod mix compile
npm run deploy --prefix ./assets
mix phx.digest
MIX_ENV=prod mix release
  1. bash release.sh

Run Phoenix app as service

  1. sudo apt install policykit-1
  2. Create the spacerocket service: sudo nano /lib/systemd/system/spacerocket.service
/lib/systemd/system/spacerocket.service
[Unit]
Description=Space Rocket app daemon
 
[Service]
Type=simple
User=admin
Group=admin
Restart=on-failure
Environment=MIX_ENV=prod
Environment=LANG=en_US.UTF-8
Environment=PORT=80
 
WorkingDirectory=/var/apps/space-rocket.dev

ExecStart=/var/apps/space-rocket.dev/app/space-rocket-phx/_build/prod/rel/space_rocket/bin/space_rocket start
ExecStop=/var/apps/space-rocket.dev/app/space-rocket-phx/_build/prod/rel/space_rocket/bin/space_rocket stop

[Install]
WantedBy=multi-user.target
  1. sudo systemctl enable spacerocket.service
  2. sudo systemctl status spacerocket.service
  3. sudo journalctl -u spacerocket.service --since today
  4. sudo systemctl daemon-reload
  5. sudo systemctl restart spacerocket.service