DEV Community 👩‍💻👨‍💻

Cover image for Rapid Docker on AWS: How to set up the AWS infrastructure?
Andreas Wittig
Andreas Wittig

Posted on

Rapid Docker on AWS: How to set up the AWS infrastructure?

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

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:

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:

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):

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
This post is an excerpt from our new book Rapid Docker on AWS. The book includes code samples for PHP, Ruby (Rails), Python (Django), Java (Spring Boot), and Node.js (Express).

Top comments (0)

Take a look at this:

Settings

Go to your customization settings to nudge your home feed to show content more relevant to your developer experience level. 🛠