Conduit (Serverless & CloudFront) Quickstart

Deploying the Conduit Web App

The Medium.com-clone “RealWorld” demo app named Conduit provides a simple demonstration of using Runway to deploy a Serverless Framework backend with an Angular frontend.

Prerequisites

  • An AWS account, and configured terminal environment for interacting with it with an admin role.

  • The following installed tools:

    • npm

    • yarn

    • git (Available out of the box on macOS)

Setup

  1. Prepare the project directory. See Repo Structure for more details.

    mkdir conduit
    cd conduit
    git init
    git checkout -b ENV-dev
    
  2. Download/install Runway. Here we are showing the curl option. To see other available install methods, see Installation.

    macOS

    curl -L https://oni.ca/runway/latest/osx -o runway
    chmod +x runway
    

    Ubuntu

    curl -L https://oni.ca/runway/latest/linux -o runway
    chmod +x runway
    

    Windows

    iwr -Uri oni.ca/runway/latest/windows -OutFile runway.exe
    
  3. Download the source files.

    macOS/Linux

    curl -O https://codeload.github.com/anishkny/realworld-dynamodb-lambda/zip/v1.0.0
    unzip v1.0.0
    rm v1.0.0
    mv realworld-dynamodb-lambda-1.0.0 backend
    cd backend
    sed -i '/package-lock\.json/d' .gitignore
    echo '.dynamodb' >> .gitignore
    npm install
    cd ..
    curl -O https://codeload.github.com/gothinkster/angular-realworld-example-app/zip/35a66d144d8def340278cd55080d5c745714aca4
    unzip 35a66d144d8def340278cd55080d5c745714aca4
    rm 35a66d144d8def340278cd55080d5c745714aca4
    mv angular-realworld-example-app-35a66d144d8def340278cd55080d5c745714aca4 frontend
    cd frontend
    mkdir scripts
    cd scripts && { curl -O https://raw.githubusercontent.com/onicagroup/runway/master/quickstarts/conduit/build.js ; cd -; }
    sed -i 's/^\s*"build":\s.*$/    "build": "node scripts\/build",/' package.json
    sed -i 's/^\s*"rxjs":\s.*$/    "rxjs": "~6.3.3",/' package.json
    npm install
    curl -O https://raw.githubusercontent.com/onicagroup/runway/master/quickstarts/conduit/update_env_endpoint.py
    cd ..
    curl -O https://raw.githubusercontent.com/onicagroup/runway/master/quickstarts/conduit/runway.yml
    

    Windows

    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    Invoke-WebRequest https://codeload.github.com/anishkny/realworld-dynamodb-lambda/zip/v1.0.0 -OutFile v1.0.0.zip
    Expand-Archive v1.0.0.zip .
    Remove-Item v1.0.0.zip -Force
    Rename-Item realworld-dynamodb-lambda-1.0.0 backend
    cd backend
    (gc .\.gitignore -raw).Replace("package-lock.json`n", "") | sc .\.gitignore
    ".dynamodb`r`n" | Out-File .\.gitignore -Append -Encoding UTF8
    $(gc .\package.json) -replace "dynamodb install .*$", "dynamodb install`"" | Out-File .\package.json -Force -Encoding UTF8
    npm install
    cd ..
    Invoke-WebRequest https://codeload.github.com/gothinkster/angular-realworld-example-app/zip/35a66d144d8def340278cd55080d5c745714aca4 -OutFile 35a66d144d8def340278cd55080d5c745714aca4.zip
    Expand-Archive 35a66d144d8def340278cd55080d5c745714aca4.zip .
    Remove-Item 35a66d144d8def340278cd55080d5c745714aca4.zip -Force
    Rename-Item angular-realworld-example-app-35a66d144d8def340278cd55080d5c745714aca4 frontend
    cd frontend
    (gc .\package.json -raw).Replace("`"rxjs`": `"^6.2.1`"", "`"rxjs`": `"~6.3.3`"") | sc .\package.json
    mkdir scripts
    Invoke-WebRequest https://raw.githubusercontent.com/onicagroup/runway/master/quickstarts/conduit/build.js -OutFile scripts/build.js
    $(gc .\package.json) -replace "^\s*`"build`":\s.*$", "    `"build`": `"node scripts/build`"," | Out-File .\package.json -Force -Encoding UTF8
    npm install
    Invoke-WebRequest https://raw.githubusercontent.com/onicagroup/runway/master/quickstarts/conduit/update_env_endpoint.py -OutFile update_env_endpoint.py
    cd ..
    Invoke-WebRequest https://raw.githubusercontent.com/onicagroup/runway/master/quickstarts/conduit/Pipfile -OutFile Pipfile
    Invoke-WebRequest https://raw.githubusercontent.com/onicagroup/runway/master/quickstarts/conduit/runway.yml -OutFile runway.yml
    

Deploying

Execute pipenv run runway deploy, enter all (to deploy the backend followed by the frontend). Deployment will take some time (mostly waiting for the CloudFront distribution to stabilize).

The CloudFront domain at which the site can be reached will be displayed near the last lines of output once deployment is complete, e.g.:

staticsite: sync & CF invalidation of E17B5JWPMTX5Z8 (domain ddy1q4je03d7u.cloudfront.net) complete

Teardown

Execute pipenv run runway destroy, enter all.

The backend DynamoDB tables will still be retained after the destroy is complete. They must be deleted separately:

On macOS/Linux:

for i in realworld-dev-articles realworld-dev-comments realworld-dev-users; do aws dynamodb delete-table --region us-east-1 --table-name $i; done

On Windows:

foreach($table in @("realworld-dev-articles", "realworld-dev-comments", "realworld-dev-users"))
{
  CMD /C "pipenv run aws dynamodb delete-table --region us-east-1 --table-name $table"
}

Next Steps / Additional Notes

The serverless-plugin-export-endpoints plugin is a good alternative to the custom update_env_endpoint.py script deployed above to update the environment file.

Permissions

The specific IAM permissions required to manage the resources in this demo are as follows

# CloudFormation
- cloudformation:CreateStack
- cloudformation:DeleteStack
- cloudformation:CreateChangeSet
- cloudformation:DescribeChangeSet
- cloudformation:DeleteChangeSet
- cloudformation:DescribeStackResource
- cloudformation:DescribeStackResources
- cloudformation:DescribeStacks
- cloudformation:DescribeStackEvents
- cloudformation:GetTemplate
- cloudformation:UpdateStack
- cloudformation:ExecuteChangeSet
- cloudformation:ValidateTemplate
# Serverless
- apigateway:GET
- apigateway:DELETE
- apigateway:POST
- apigateway:PUT
- lambda:AddPermission
- lambda:CreateAlias
- lambda:CreateFunction
- lambda:DeleteAlias
- lambda:DeleteFunction
- lambda:GetFunction
- lambda:GetFunctionConfiguration
- lambda:ListVersionsByFunction
- lambda:PublishVersion
- lambda:UpdateAlias
- lambda:UpdateFunctionCode
- lambda:UpdateFunctionConfiguration
- iam:CreateRole
- iam:DeleteRole
- iam:DeleteRolePolicy
- iam:GetRole
- iam:PassRole
- iam:PutRolePolicy
- logs:CreateLogGroup
- logs:DeleteLogGroup
- logs:DescribeLogGroups
- s3:CreateBucket
- s3:DeleteBucket
- s3:DeleteBucketPolicy
- s3:DeleteObject
- s3:DeleteObjectVersion
- s3:GetObjectVersion
- s3:ListBucket
- s3:ListBucketVersions
- s3:PutBucketVersioning
- s3:PutBucketPolicy
- s3:PutLifecycleConfiguration
# Frontend
- cloudfront:CreateCloudFrontOriginAccessIdentity
- cloudfront:CreateDistribution
- cloudfront:CreateInvalidation
- cloudfront:DeleteCloudFrontOriginAccessIdentity
- cloudfront:DeleteDistribution
- cloudfront:GetCloudFrontOriginAccessIdentity
- cloudfront:GetCloudFrontOriginAccessIdentityConfig
- cloudfront:GetDistribution
- cloudfront:GetDistributionConfig
- cloudfront:GetInvalidation
- cloudfront:ListDistributions
- cloudfront:TagResource
- cloudfront:UntagResource
- cloudfront:UpdateCloudFrontOriginAccessIdentity
- cloudfront:UpdateDistribution
- s3:DeleteBucketWebsite
- s3:GetBucketAcl
- s3:GetObject
- s3:PutBucketAcl
- s3:GetBucketWebsite
- s3:PutBucketWebsite
- s3:PutObject
- ssm:GetParameter
- ssm:PutParameter
# Backend
- dynamodb:CreateTable
- dynamodb:DeleteTable
- dynamodb:DescribeTable
- dynamodb:TagResource
- dynamodb:UntagResource
- dynamodb:UpdateTable