Rapid Docker on AWS: How to set up the AWS infrastructure?
Tags: aws, docker, devops
Published: 2019-11-07 19:17:51

The secret sauce allowing you to set up your cloud infrastructure within minutes, which consists of around 100 resources, is Infrastructure as Code. It removes the need for clicking through the AWS Management Console manually.

In our opinion, Infrastructure as Code works best when following the declarative approach. Define the target state in source code and use a tool which calculates and executes the needed steps to transform the current state into the target state. Our tool of choice is AWS CloudFormation.

The figure below explains the key concepts of CloudFormation.

* You create a JSON or YAML file describing the target state of your infrastructure, which CloudFormation calls a **template**.
* You upload your template and ask CloudFormation to create, update or delete your infrastructure. The state of your infrastructure is stored within a so-called **stack**.
* CloudFormation transforms the current state of your stack into the target state defined in your template. To do so, CloudFormation creates, updates, or deletes **resources** as needed.

![Infrastructure as Code with CloudFormation](https://thepracticaldev.s3.amazonaws.com/i/dtd91syrpw8floqxfw6i.png)

First, install the CloudFormation modules. To do so, create a `package.json` file with the following content.

```
{
  "name": "rapid-docker-on-aws-rapid-docker-on-aws",
  "version": "1.0.0",
  "description": "Rapid Docker on AWS: Demo",
  "author": "Michael Wittig <michael@widdix.de>",
  "license": "Apache-2.0",
  "private": true,
  "dependencies": {
    "@cfn-modules/alb": "1.0.4",
    "@cfn-modules/alb-listener": "1.0.0",
    "@cfn-modules/alerting": "1.2.0",
    "@cfn-modules/client-sg": "1.0.0",
    "@cfn-modules/cloudwatch-dashboard": "1.2.0",
    "@cfn-modules/ecs-cluster": "1.1.0",
    "@cfn-modules/ecs-alb-target": "1.2.0",
    "@cfn-modules/fargate-service": "2.5.0",
    "@cfn-modules/kms-key": "1.2.0",
    "@cfn-modules/rds-aurora-serverless": "1.5.0",
    "@cfn-modules/secret": "1.3.0",
    "@cfn-modules/vpc": "1.1.1"
  }
}
```

Next, create the CloudFormation template. To do so, create a `template.yml` file with the following content.

```
---
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Rapid Docker on AWS: Demo application'
Parameters:
  AppImage:
    Description: 'The Docker image to use for the app container.'
    Type: String
    Default: 'cloudonaut/docker-on-aws-rapid-docker-on-aws-php-fpm:latest'
  ProxyImage:
    Description: 'Docker image to use for the proxy container.'
    Type: String
    Default: 'cloudonaut/docker-on-aws-rapid-docker-on-aws-nginx:latest'
  AdminEmail:
    Description: 'Optional email address of the administrator.'
    Type: String
    Default: ''
Conditions:
  HasAdminEmail: !Not [!Equals ['', !Ref AdminEmail]]
Resources:
  Alerting:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      Parameters:
        Email: !If [HasAdminEmail, !Ref AdminEmail, !Ref 'AWS::NoValue']
      TemplateURL: './node_modules/@cfn-modules/alerting/module.yml'
  Dashboard:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      Parameters:
        DashboardName: !Ref 'AWS::StackName'
        AlbModule: !GetAtt 'Alb.Outputs.StackName'
        FargateServiceModule: !GetAtt 'AppService.Outputs.StackName'
        RdsAuroraServerlessModule: !GetAtt 'AuroraServerlessCluster.Outputs.StackName'
      TemplateURL: './node_modules/@cfn-modules/cloudwatch-dashboard/module.yml'
  Key:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      Parameters:
        AlertingModule: !GetAtt 'Alerting.Outputs.StackName'
      TemplateURL: './node_modules/@cfn-modules/kms-key/module.yml'
  DatabaseSecret:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      Parameters:
        KmsKeyModule: !GetAtt 'Key.Outputs.StackName'
        Description: !Sub '${AWS::StackName}: database password'
      TemplateURL: './node_modules/@cfn-modules/secret/module.yml'
  Vpc:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      Parameters:
        AlertingModule: !GetAtt 'Alerting.Outputs.StackName'
        NatGateways: 'false' # reduce costs
      TemplateURL: './node_modules/@cfn-modules/vpc/module.yml'
  AuroraServerlessClientSg:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      Parameters:
        VpcModule: !GetAtt 'Vpc.Outputs.StackName'
      TemplateURL: './node_modules/@cfn-modules/client-sg/module.yml'
  AuroraServerlessCluster:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      Parameters:
        VpcModule: !GetAtt 'Vpc.Outputs.StackName'
        ClientSgModule: !GetAtt 'AuroraServerlessClientSg.Outputs.StackName'
        KmsKeyModule: !GetAtt 'Key.Outputs.StackName'
        AlertingModule: !GetAtt 'Alerting.Outputs.StackName'
        SecretModule: !GetAtt 'DatabaseSecret.Outputs.StackName'
        DBName: test
        DBMasterUsername: master
        SecondsUntilAutoPause: '900'
        MinCapacity: '1'
        MaxCapacity: '2'
        EngineVersion: '5.6.10a'
      TemplateURL: './node_modules/@cfn-modules/rds-aurora-serverless/module.yml'
  Alb:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      Parameters:
        VpcModule: !GetAtt 'Vpc.Outputs.StackName'
        AlertingModule: !GetAtt 'Alerting.Outputs.StackName'
      TemplateURL: './node_modules/@cfn-modules/alb/module.yml'
  AlbListenerHttp:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      Parameters:
        AlbModule: !GetAtt 'Alb.Outputs.StackName'
        Port: '80'
      TemplateURL: './node_modules/@cfn-modules/alb-listener/module.yml'
  AppTarget:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      Parameters:
        AlbModule: !GetAtt 'Alb.Outputs.StackName'
        AlbListenerModule: !GetAtt 'AlbListenerHttp.Outputs.StackName'
        VpcModule: !GetAtt 'Vpc.Outputs.StackName'
        AlertingModule: !GetAtt 'Alerting.Outputs.StackName'
        Priority: '2'
        HealthCheckPath: '/health-check.php'
      TemplateURL: './node_modules/@cfn-modules/ecs-alb-target/module.yml'
  Cluster:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      TemplateURL: './node_modules/@cfn-modules/ecs-cluster/module.yml'
  AppService:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      Parameters:
        VpcModule: !GetAtt 'Vpc.Outputs.StackName'
        ClusterModule: !GetAtt 'Cluster.Outputs.StackName'
        TargetModule: !GetAtt 'AppTarget.Outputs.StackName'
        AlertingModule: !GetAtt 'Alerting.Outputs.StackName'
        ClientSgModule1: !GetAtt 'AuroraServerlessClientSg.Outputs.StackName'
        ProxyImage: !Ref ProxyImage
        ProxyPort: '80'
        AppImage: !Ref AppImage
        AppPort: '9000'
        AppEnvironment1Key: 'DATABASE_PASSWORD'
        AppEnvironment1SecretModule: !GetAtt 'DatabaseSecret.Outputs.StackName'
        AppEnvironment2Key: 'DATABASE_HOST'
        AppEnvironment2Value: !GetAtt 'AuroraServerlessCluster.Outputs.DnsName'
        AppEnvironment3Key: 'DATABASE_NAME'
        AppEnvironment3Value: 'test'
        AppEnvironment4Key: 'DATABASE_USER'
        AppEnvironment4Value: 'master'
        Cpu: '0.25'
        Memory: '0.5'
        DesiredCount: '2'
        MaxCapacity: '4'
        MinCapacity: '2'
        LogsRetentionInDays: '14'
      TemplateURL: './node_modules/@cfn-modules/fargate-service/module.yml'
Outputs:
  Url:
    Value: !Sub 'http://${Alb.Outputs.DnsName}/'
  AlbDnsName:
    Value: !GetAtt 'Alb.Outputs.DnsName'
  DashboardUrl:
    Value: !Sub 'https://${AWS::Region}.console.aws.amazon.com/cloudwatch/home?region=${AWS::Region}#dashboards:name=${AWS::StackName}'
  AppLogsUrl:
    Value: !Sub "https://${AWS::Region}.console.aws.amazon.com/cloudwatch/home?region=${AWS::Region}#logs-insights:queryDetail=~(source~(~'${AppService.Outputs.LogGroupName}))"
```

Finally, you will deploy the demo web application into your AWS account.

> **Warning** Costs arise when you launch the demo infrastructure and application. You can expect costs of around $4.50 per day.

In your temporary working environment (replace `$NICKNAME`), execute the following commands:

```bash
npm i
aws s3 mb s3://rapid-docker-on-aws-$NICKNAME
aws cloudformation package --template-file template.yml \
  --s3-bucket rapid-docker-on-aws-$NICKNAME \
  --output-template-file .template.yml
aws cloudformation deploy --template-file .template.yml \
  --stack-name rapid-docker-on-aws --capabilities CAPABILITY_IAM
```

The last command takes around 20 minutes to complete with an output like this:

```
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - rapid-docker-on-aws
```

The AWS resources are created. The following command fetches the URL:

```bash
aws cloudformation describe-stacks --stack-name rapid-docker-on-aws \
  --query 'Stacks[0].Outputs[?OutputKey==`Url`].OutputValue' \
  --output text
```

The output looks like this:

```
http://demo-LoadB-[...].elb.amazonaws.com
```

Open the URL in your web browser. Remember all the benefits of the Rapid Docker on AWS architecture. Isn't it amazing how easy you can launch this architecture?

It's time to delete the running demo web application to avoid future costs. Execute the following commands (replace `$NICKNAME`):

```bash
aws cloudformation delete-stack --stack-name rapid-docker-on-aws
aws s3 rb --force s3://rapid-docker-on-aws-$NICKNAME
```

*Do you have any questions? Please leave them in the comments. This is the 4th post of a series. Follow me to make sure you are not missing the following posts.*

> ![Rapid Docker on AWS](https://thepracticaldev.s3.amazonaws.com/i/edqm6fcngiasifd8tmj7.jpg)
> This post is an excerpt from our new book [Rapid Docker on AWS](https://cloudonaut.io/rapid-docker-on-aws/?utm_source=devto&utm_medium=post&utm_campaign=rapid-docker-on-aws-series). The book includes code samples for PHP, Ruby (Rails), Python (Django), Java (Spring Boot), and Node.js (Express).