> For the complete documentation index, see [llms.txt](https://tutorial.specian.co.uk/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tutorial.specian.co.uk/devops/aws/cloudfront.md).

# CloudFormation

Everything can be done in the UI on console.aws.amazon.com. But we want to automate as much as possible.

For this reason, we won't create any resources manually anymore.

We will use [CloudFormation](https://aws.amazon.com/cloudformation/) instead so we can keep the infrastructure configuration under version control.

{% hint style="info" %}
Read more [Infrastructure as code](https://en.wikipedia.org/wiki/Infrastructure_as_code).
{% endhint %}

* If something went wrong, CloudFormation will **automatically rollback** the changes.
* You can delete all the resources created with one click.
* Use [detect drift](https://aws.amazon.com/blogs/aws/new-cloudformation-drift-detection/) to see if something was changed manually instead of using CloudFormation.

![You can follow the progress for your changes](/files/-LrFcyFHPOAhROWGMoz3)

![You can delete all the resources created and detect drift](/files/-LrFdKr0x2lfkx1C-9kX)

### How to use CloudFormation

You can use `yml` or `json` files. For `yml` files, resources take the following format:

```
ResourceName:
    Type: service-provider::service-name::data-type-name
    Properties:
        ...
```

{% hint style="info" %}
You can find all the options available at [AWS Resource and Property Types Reference](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html).
{% endhint %}

Example to create an RDS database using [AWS::RDS::DBCluster](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbcluster.html):

```yaml
ServerlessRDSCluster:
    Type: AWS::RDS::DBCluster
    Properties:
        Engine: aurora
        EngineMode: serverless
        Port: 3306
        DatabaseName: testdb
        MasterUsername: myuser
        MasterUserPassword: secret
        ScalingConfiguration:
            AutoPause: False
            MinCapacity: 2
            MaxCapacity: 256
```

### Creating the resources

We can manage CloudFormation resources from `serverless.yml`.

Here's the full file to create everything needed:

{% code title="serverless.yml" %}

```yaml
service: daedalost

provider:
  region: eu-west-1
  stage: ${opt:stage, 'dev'}
  name: aws
  runtime: nodejs10.x
  memorySize: 1024
  timeout: 30
  vpc:
    securityGroupIds:
      - Fn::GetAtt: [VPCStaticIP, DefaultSecurityGroup]
    subnetIds:
      - Ref: SubnetPrivate
      - Ref: SubnetPrivate2
  iamRoleStatements:
    - Effect: Allow
      Action:
        - ec2:CreateNetworkInterface
        - ec2:DeleteNetworkInterface
        - ec2:DescribeNetworkInterfaces
      Resource: "*"
  environment:
    SERVERLESS_EXPRESS_PLATFORM: aws
    NODE_ENV: production
    STAGE: ${opt:stage, 'development'}
    POSTGRES_DATABASE: ${self:service}
    POSTGRES_HOST:
      Fn::GetAtt:
        - ServerlessRDSCluster
        - Endpoint.Address
    POSTGRES_USERNAME: ${ssm:/${self:service}/${self:provider.stage}/POSTGRES_USERNAME}
    POSTGRES_PASSWORD: ${ssm:/${self:service}/${self:provider.stage}/POSTGRES_PASSWORD}
    JWT_SECRET: ${ssm:/${self:service}/${self:provider.stage}/JWT_SECRET}

plugins:
  - serverless-plugin-typescript
  - serverless-offline
  - serverless-finch

custom:
  client:
    bucketName: daedalost
    distributionFolder: ../web/build
    errorDocument: index.html

functions:
  server:
    handler: server.handler
    events:
      - http:
          path: /{proxy+}
          method: any
          cors: true

resources:
  Resources:
    # Step 1: Create a new VPC
    VPCStaticIP:
      Type: AWS::EC2::VPC
      Properties:
        CidrBlock: 11.0.0.0/16
        Tags:
          - Key: Name
            Value: ${self:service}-${self:provider.stage}-vpc

    # Step 2: Create 2 Subnets
    SubnetPublic:
      Type: AWS::EC2::Subnet
      Properties:
        AvailabilityZone: ${self:provider.region}a
        CidrBlock: 11.0.0.0/24
        Tags:
          - Key: Name
            Value: ${self:service}-${self:provider.stage}-public-subnet
        VpcId:
          Ref: VPCStaticIP

    SubnetPrivate:
      Type: AWS::EC2::Subnet
      Properties:
        AvailabilityZone: ${self:provider.region}b
        CidrBlock: 11.0.1.0/24
        Tags:
          - Key: Name
            Value: ${self:service}-${self:provider.stage}-private-subnet-b
        VpcId:
          Ref: VPCStaticIP

    SubnetPrivate2:
      Type: AWS::EC2::Subnet
      Properties:
        AvailabilityZone: ${self:provider.region}c
        CidrBlock: 11.0.2.0/24
        Tags:
          - Key: Name
            Value: ${self:service}-${self:provider.stage}-private-subnet-c
        VpcId:
          Ref: VPCStaticIP

    # Step 3: Create an Internet Gateway
    InternetGateway:
      Type: AWS::EC2::InternetGateway
      Properties:
        Tags:
          - Key: Name
            Value: ${self:service}-${self:provider.stage}-igw

    # Attach Internet Gateway to VPC
    VPCGatewayAttachment:
      Type: AWS::EC2::VPCGatewayAttachment
      Properties:
        InternetGatewayId:
          Ref: InternetGateway
        VpcId:
          Ref: VPCStaticIP

    # Step 4: Create a public Route Table and Assign it to our public route
    RouteTablePublic:
      Type: AWS::EC2::RouteTable
      Properties:
        VpcId:
          Ref: VPCStaticIP
        Tags:
          - Key: Name
            Value: ${self:service}-${self:provider.stage}-public-route

    RoutePublic:
      Type: AWS::EC2::Route
      Properties:
        DestinationCidrBlock: 0.0.0.0/0
        GatewayId:
          Ref: InternetGateway
        RouteTableId:
          Ref: RouteTablePublic

    SubnetRouteTableAssociationPublic:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId:
          Ref: RouteTablePublic
        SubnetId:
          Ref: SubnetPublic

    # Step 5: Create a NAT Gateway
    # Before creating NAT Gateway, we need to create Elastic IP with vpc scope
    EIP:
      Type: AWS::EC2::EIP
      Properties:
        Domain: vpc

    NatGateway:
      Type: AWS::EC2::NatGateway
      Properties:
        AllocationId:
          Fn::GetAtt: [EIP, AllocationId]
        SubnetId:
          Ref: SubnetPublic

    RouteTablePrivate:
      Type: AWS::EC2::RouteTable
      Properties:
        VpcId:
          Ref: VPCStaticIP
        Tags:
          - Key: Name
            Value: ${self:service}-${self:provider.stage}-private-route

    RoutePrivate:
      Type: AWS::EC2::Route
      Properties:
        DestinationCidrBlock: 0.0.0.0/0
        NatGatewayId:
          Ref: NatGateway
        RouteTableId:
          Ref: RouteTablePrivate

    SubnetRouteTableMainAssociationPrivate:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId:
          Ref: RouteTablePrivate
        SubnetId:
          Ref: SubnetPrivate

    SubnetRouteTableMainAssociationPrivate2:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId:
          Ref: RouteTablePrivate
        SubnetId:
          Ref: SubnetPrivate2

    DatabaseSubnetGroup:
      Type: AWS::RDS::DBSubnetGroup
      Properties:
        DBSubnetGroupDescription: Db subnet for ${self:service}
        SubnetIds:
          - Ref: SubnetPrivate
          - Ref: SubnetPrivate2

    RDSSecurityGroup:
      Type: "AWS::EC2::SecurityGroup"
      Properties:
        GroupDescription: SecurityGroup ${self:service}/${self:provider.stage}
        SecurityGroupIngress:
          - IpProtocol: tcp
            FromPort: 3306
            ToPort: 3306
            SourceSecurityGroupId:
              Fn::GetAtt: [VPCStaticIP, DefaultSecurityGroup]
        VpcId:
          Ref: VPCStaticIP

    ServerlessRDSCluster:
      Type: AWS::RDS::DBCluster
      Properties:
        DBSubnetGroupName:
          Ref: DatabaseSubnetGroup
        Engine: aurora-postgresql
        EngineMode: serverless
        DatabaseName: ${self:service}
        MasterUsername: ${ssm:/${self:service}/${self:provider.stage}/POSTGRES_USERNAME}
        MasterUserPassword: ${ssm:/${self:service}/${self:provider.stage}/POSTGRES_PASSWORD}
        VpcSecurityGroupIds:
          - Ref: RDSSecurityGroup
        ScalingConfiguration:
          AutoPause: False
          MinCapacity: 2
          MaxCapacity: 256
```

{% endcode %}

{% hint style="info" %}
Read the documentation about [Serverless Variables](https://serverless.com/framework/docs/providers/aws/guide/variables/).
{% endhint %}

{% hint style="info" %}
Read serverless blog post about [Managing secrets, API keys and more with Serverless](https://serverless.com/blog/serverless-secrets-api-keys/).
{% endhint %}

{% hint style="info" %}
Configure the VPC manually with the console by following this step by step tutorial [Configure and Connect to Serverless MySQL Database](https://aws.amazon.com/getting-started/tutorials/configure-connect-serverless-mysql-database-aurora/).
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://tutorial.specian.co.uk/devops/aws/cloudfront.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
