├── .github └── workflows │ ├── check.yml │ └── deploy.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── cloudformation-templates ├── infrastructure.yml ├── service.yml └── setup.yml └── index.html /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | # This GitHub Actions workflow checks that the docker image still builds 2 | # successfully on every pull request to the repository. 3 | 4 | on: 5 | [pull_request] 6 | 7 | name: Check 8 | 9 | jobs: 10 | check: 11 | name: Build Image 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Build image 18 | run: | 19 | docker build . 20 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # This GitHub Actions workflow runs on every push to the master branch of the 2 | # repository, and deploys a full containerized application on AWS. 3 | # 4 | # First, the workflow deploys AWS infrastructure resources from AWS 5 | # CloudFormation templates, including a public load balancer and container 6 | # image repository. 7 | # 8 | # Then, the workflow builds and deploys the Docker image for one or more 9 | # microservices, using a CloudFormation template to deploy the image to an 10 | # Amazon ECS service. 11 | 12 | on: 13 | push: 14 | branches: 15 | - master 16 | 17 | name: Deploy 18 | 19 | jobs: 20 | # Deploy infrastructure resources like ECR repository and load balancer. 21 | # This job will run on every code change to the master branch, but will only deploy 22 | # changes if the infrastructure CloudFormation template in the repository have changed. 23 | deploy-infrastructure: 24 | name: Deploy infrastructure 25 | runs-on: ubuntu-latest 26 | permissions: 27 | id-token: write 28 | contents: read 29 | outputs: 30 | env-name: ${{ steps.env-name.outputs.environment }} 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | 35 | # The "environment name" is used as the base for CloudFormation stack names, 36 | # and is derived from the GitHub repository name. 37 | # For example, the repo 'Octocat/Hello-World' would have the environment name 38 | # 'Octocat-Hello-World' and would deploy a stack name 'Octocat-Hello-World-ecr-repo'. 39 | - name: Configure environment name 40 | id: env-name 41 | env: 42 | REPO: ${{ github.repository }} 43 | run: | 44 | ENVIRONMENT=`echo $REPO | tr "/" "-"` 45 | echo "Environment name: $ENVIRONMENT" 46 | echo "environment=$ENVIRONMENT" >> "$GITHUB_OUTPUT" 47 | 48 | - name: Configure AWS credentials 49 | id: creds 50 | uses: aws-actions/configure-aws-credentials@v4 51 | with: 52 | role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-cloudformation-deploy-role 53 | role-session-name: DeployInfra 54 | aws-region: us-east-2 55 | 56 | - name: Retrieve default VPC ID and public subnets 57 | id: vpc 58 | run: | 59 | VPC_ID=`aws ec2 describe-vpcs --filters "Name=isDefault, Values=true" --query 'Vpcs[].VpcId' --output text` 60 | echo "vpc-id=$VPC_ID" >> "$GITHUB_OUTPUT" 61 | 62 | SUBNET_1=`aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" "Name=default-for-az,Values=true" --query 'Subnets[0].SubnetId' --output text` 63 | echo "subnet-one=$SUBNET_1" >> "$GITHUB_OUTPUT" 64 | 65 | SUBNET_2=`aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" "Name=default-for-az,Values=true" --query 'Subnets[1].SubnetId' --output text` 66 | echo "subnet-two=$SUBNET_2" >> "$GITHUB_OUTPUT" 67 | 68 | - name: Deploy infrastructure with CloudFormation 69 | id: infrastructure-stack 70 | uses: aws-actions/aws-cloudformation-github-deploy@v1 71 | with: 72 | name: ${{ steps.env-name.outputs.environment }}-infra 73 | template: cloudformation-templates/infrastructure.yml 74 | role-arn: arn:aws:iam::${{ steps.creds.outputs.aws-account-id }}:role/github-actions-cloudformation-stack-role 75 | no-fail-on-empty-changeset: "1" 76 | parameter-overrides: >- 77 | EnvironmentName=${{ steps.env-name.outputs.environment }}, 78 | VPC=${{ steps.vpc.outputs.vpc-id }}, 79 | PublicSubnetOne=${{ steps.vpc.outputs.subnet-one }}, 80 | PublicSubnetTwo=${{ steps.vpc.outputs.subnet-two }} 81 | 82 | # Build and deploy a Docker image to an ECS service through CloudFormation. 83 | # 84 | # This job can be copied multiple times to deploy different microservices 85 | # within the same application. For example, if there is another Dockerfile 86 | # for another microservice in a 'backend-service' folder in the repository, 87 | # another job named 'deploy-backend-service' can be added to this workflow 88 | # which builds that Dockerfile and deploys the image to a 'backend-service' 89 | # ECS service in a separate CloudFormation stack. 90 | deploy-web-app: 91 | name: Deploy web application 92 | runs-on: ubuntu-latest 93 | permissions: 94 | id-token: write 95 | contents: read 96 | needs: deploy-infrastructure 97 | steps: 98 | - name: Checkout 99 | uses: actions/checkout@v4 100 | 101 | - name: Configure AWS credentials 102 | id: creds 103 | uses: aws-actions/configure-aws-credentials@v4 104 | with: 105 | role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-cloudformation-deploy-role 106 | role-session-name: DeployWebApp 107 | aws-region: us-east-2 108 | 109 | - name: Login ECR 110 | id: login-ecr 111 | uses: aws-actions/amazon-ecr-login@v2 112 | 113 | # When copying this job to add another microservice, update the image tag value 114 | # below (for example, 'backend' instead of 'webapp'). Also update the 'docker build' 115 | # command to build the new microservice's Dockerfile in the repository. 116 | - name: Build, tag, and push webapp image 117 | id: build-image 118 | env: 119 | ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} 120 | ECR_REPOSITORY: github-actions-${{ needs.deploy-infrastructure.outputs.env-name }} 121 | IMAGE_TAG: webapp-${{ github.sha }} 122 | run: | 123 | docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . 124 | docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG 125 | echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> "$GITHUB_OUTPUT" 126 | 127 | # When copying this job to add another microservice, update the name of the stack and the 128 | # service name below (for example, 'backend' instead of 'webapp'). Also add parameter 129 | # overrides below named 'LoadBalancerPath' and 'LoadBalancerPriority' (for example, 130 | # 'LoadBalancerPath=/backend/*,LoadBalancerPriority=2'). 131 | - name: Deploy ECS service with CloudFormation 132 | id: service-stack 133 | uses: aws-actions/aws-cloudformation-github-deploy@v1 134 | with: 135 | name: ${{ needs.deploy-infrastructure.outputs.env-name }}-webapp 136 | template: cloudformation-templates/service.yml 137 | role-arn: arn:aws:iam::${{ steps.creds.outputs.aws-account-id }}:role/github-actions-cloudformation-stack-role 138 | parameter-overrides: >- 139 | EnvironmentName=${{ needs.deploy-infrastructure.outputs.env-name }}, 140 | ServiceName=webapp, 141 | ImageUrl=${{ steps.build-image.outputs.image }} 142 | 143 | - name: Print service URL 144 | env: 145 | URL: ${{ steps.service-stack.outputs.ServiceURL }} 146 | run: | 147 | echo "Service URL: $URL" 148 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | 3 | COPY index.html /usr/share/nginx/html 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS CloudFormation Starter Workflow for GitHub Actions 2 | 3 | This template repository contains a sample application and sample GitHub Actions workflow files for continuously deploying both application code and infrastructure as code with GitHub Actions. 4 | 5 | The sample application is a simple containerized web application that uses Amazon ECS on AWS Fargate behind a public Application Load Balancer (ALB). The application is split up into two CloudFormation stacks: 6 | 1. [infrastructure.yml](cloudformation-templates/infrastructure.yml) deploys the infrastructure resources (for example, the ALB and the ECS cluster) 7 | 1. [service.yml](cloudformation-templates/service.yml) deploys the application code (for example, the ECS task definition and ECS service) 8 | 9 | This repository contains two starter workflow files for GitHub Actions: 10 | 1. [check.yml](.github/workflows/check.yml) runs when a pull request is opened or updated. This workflow validates that the web application Docker image builds successfully with the proposed code changes. 11 | 1. [deploy.yml](.github/workflows/deploy.yml) runs when a new commit is pushed to the master branch. This workflow deploys both the infrastucture CloudFormation stack and the application CloudFormation stack. 12 | 13 | ## Create a GitHub repository from this template 14 | 15 | Click the "Use this template" button above to create a new repository from this template. 16 | 17 | Clone your new repository, and deploy the IAM resources needed to enable GitHub Actions to deploy CloudFormation templates. Replace the placeholder values for your GitHub org/user name and repository name below: 18 | ``` 19 | aws cloudformation deploy \ 20 | --stack-name github-actions-cloudformation-deploy-setup \ 21 | --template-file cloudformation-templates/setup.yml \ 22 | --capabilities CAPABILITY_NAMED_IAM \ 23 | --region us-east-2 \ 24 | --parameter-overrides GitHubOrg=my-github-org RepositoryName=my-repo-name 25 | ``` 26 | You can review the permissions that your repository's GitHub Actions deployment workflow will have in the [setup.yml](cloudformation-templates/setup.yml) CloudFormation template. 27 | 28 | Retrieve the account ID for the AWS account that GitHub Actions will use for deployments: 29 | ``` 30 | aws sts get-caller-identity \ 31 | --query Account \ 32 | --output text 33 | ``` 34 | 35 | Create a GitHub Actions secret named `AWS_ACCOUNT_ID` containing the account ID in your GitHub repository, 36 | by going to Settings > Secrets and variables > Actions. 37 | Alternatively, you can create these GitHub Actions secrets at the GitHub organization level, 38 | and grant access to the secrets to your new repository. 39 | 40 | Go to the Actions tab, select the latest workflow run and its failed job, then select "Re-run jobs" > "Re-run all jobs". 41 | 42 | When the workflow successfully completes, expand the "Print service URL" step in the "Deploy web application" job to see the URL for the deployed web application. 43 | 44 | ## Security 45 | 46 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 47 | 48 | ## License 49 | 50 | This library is licensed under the MIT-0 License. See the LICENSE file. 51 | -------------------------------------------------------------------------------- /cloudformation-templates/infrastructure.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: External, public facing load balancer, for forwarding public traffic to containers 3 | 4 | Parameters: 5 | EnvironmentName: 6 | Type: String 7 | Default: production 8 | Description: The name of the environment to add this load balancer to 9 | ECSTaskExecutionRole: 10 | Type: String 11 | Default: github-actions-ecs-task-execution-role 12 | Description: The name of the IAM role for ECS Task Execution 13 | VPC: 14 | Description: VPC ID 15 | Type: AWS::EC2::VPC::Id 16 | PublicSubnetOne: 17 | Description: Subnet ID 18 | Type: AWS::EC2::Subnet::Id 19 | PublicSubnetTwo: 20 | Description: Subnet ID 21 | Type: AWS::EC2::Subnet::Id 22 | 23 | Resources: 24 | ImageRepository: 25 | Type: AWS::ECR::Repository 26 | Properties: 27 | RepositoryName: !Sub "github-actions-${EnvironmentName}" 28 | 29 | # ECS Resources 30 | ECSCluster: 31 | Type: AWS::ECS::Cluster 32 | 33 | # A security group for the containers we will run in Fargate. 34 | # Rules are added to this security group based on what ingress you 35 | # add for the cluster. 36 | ContainerSecurityGroup: 37 | Type: AWS::EC2::SecurityGroup 38 | Properties: 39 | GroupDescription: Access to the Fargate containers 40 | VpcId: !Ref 'VPC' 41 | 42 | EcsSecurityGroupIngressFromPublicALB: 43 | Type: AWS::EC2::SecurityGroupIngress 44 | Properties: 45 | Description: Ingress from the public ALB 46 | GroupId: !Ref ContainerSecurityGroup 47 | IpProtocol: -1 48 | SourceSecurityGroupId: !Ref 'PublicLoadBalancerSG' 49 | 50 | # Public load balancer, hosted in public subnets that is accessible 51 | # to the public, and is intended to route traffic to one or more public 52 | # facing services. This is used for accepting traffic from the public 53 | # internet and directing it to public facing microservices 54 | PublicLoadBalancerSG: 55 | Type: AWS::EC2::SecurityGroup 56 | Properties: 57 | GroupDescription: Access to the public facing load balancer 58 | VpcId: !Ref VPC 59 | SecurityGroupIngress: 60 | # Allow access to ALB from anywhere on the internet 61 | - CidrIp: 0.0.0.0/0 62 | IpProtocol: -1 63 | 64 | PublicLoadBalancer: 65 | Type: AWS::ElasticLoadBalancingV2::LoadBalancer 66 | Properties: 67 | Scheme: internet-facing 68 | LoadBalancerAttributes: 69 | - Key: idle_timeout.timeout_seconds 70 | Value: '30' 71 | Subnets: 72 | # The load balancer is placed into the public subnets, so that traffic 73 | # from the internet can reach the load balancer directly via the internet gateway 74 | - !Ref PublicSubnetOne 75 | - !Ref PublicSubnetTwo 76 | SecurityGroups: [!Ref 'PublicLoadBalancerSG'] 77 | 78 | # A dummy target group is used to setup the ALB to just drop traffic 79 | # initially, before any real service target groups have been added. 80 | DummyTargetGroupPublic: 81 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 82 | Properties: 83 | HealthCheckIntervalSeconds: 6 84 | HealthCheckPath: / 85 | HealthCheckProtocol: HTTP 86 | HealthCheckTimeoutSeconds: 5 87 | HealthyThresholdCount: 2 88 | Port: 80 89 | Protocol: HTTP 90 | UnhealthyThresholdCount: 2 91 | VpcId: !Ref VPC 92 | 93 | PublicLoadBalancerListener: 94 | Type: AWS::ElasticLoadBalancingV2::Listener 95 | DependsOn: 96 | - PublicLoadBalancer 97 | Properties: 98 | DefaultActions: 99 | - TargetGroupArn: !Ref 'DummyTargetGroupPublic' 100 | Type: 'forward' 101 | LoadBalancerArn: !Ref 'PublicLoadBalancer' 102 | Port: 80 103 | Protocol: HTTP 104 | 105 | Outputs: 106 | PublicListener: 107 | Description: The ARN of the public load balancer's Listener 108 | Value: !Ref PublicLoadBalancerListener 109 | Export: 110 | Name: !Sub ${EnvironmentName}:PublicListener 111 | ExternalUrl: 112 | Description: The url of the external load balancer 113 | Value: !Sub http://${PublicLoadBalancer.DNSName} 114 | Export: 115 | Name: !Sub ${EnvironmentName}:ExternalUrl 116 | ClusterName: 117 | Description: The name of the ECS cluster 118 | Value: !Ref 'ECSCluster' 119 | Export: 120 | Name: !Sub ${EnvironmentName}:ClusterName 121 | ECSTaskExecutionRole: 122 | Description: The ARN of the ECS task execution role 123 | Value: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${ECSTaskExecutionRole}' 124 | Export: 125 | Name: !Sub ${EnvironmentName}:ECSTaskExecutionRole 126 | VpcId: 127 | Description: The ID of the VPC that this stack is deployed in 128 | Value: !Ref 'VPC' 129 | Export: 130 | Name: !Sub ${EnvironmentName}:VpcId 131 | PublicSubnetOne: 132 | Description: Public subnet one 133 | Value: !Ref 'PublicSubnetOne' 134 | Export: 135 | Name: !Sub ${EnvironmentName}:PublicSubnetOne 136 | PublicSubnetTwo: 137 | Description: Public subnet two 138 | Value: !Ref 'PublicSubnetTwo' 139 | Export: 140 | Name: !Sub ${EnvironmentName}:PublicSubnetTwo 141 | ContainerSecurityGroup: 142 | Description: A security group used to allow Fargate containers to receive traffic 143 | Value: !Ref 'ContainerSecurityGroup' 144 | Export: 145 | Name: !Sub ${EnvironmentName}:ContainerSecurityGroup 146 | -------------------------------------------------------------------------------- /cloudformation-templates/service.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Deploy a service on AWS Fargate, hosted in a public subnet, and accessible via a public load balancer. 3 | Parameters: 4 | EnvironmentName: 5 | Type: String 6 | Default: production 7 | Description: The name of the environment to add this service to 8 | ServiceName: 9 | Type: String 10 | Default: nginx 11 | Description: A name for the service 12 | ImageUrl: 13 | Type: String 14 | Default: nginx 15 | Description: The url of a docker image that contains the application process that 16 | will handle the traffic for this service 17 | ContainerPort: 18 | Type: Number 19 | Default: 80 20 | Description: What port number the application inside the docker container is binding to 21 | ContainerCpu: 22 | Type: Number 23 | Default: 256 24 | Description: How much CPU to give the container. 1024 is 1 CPU 25 | ContainerMemory: 26 | Type: Number 27 | Default: 512 28 | Description: How much memory in megabytes to give the container 29 | LoadBalancerPath: 30 | Type: String 31 | Default: "*" 32 | Description: A path on the load balancer that this service 33 | should be connected to. Use * to send all load balancer 34 | traffic to this service. 35 | LoadBalancerPriority: 36 | Type: Number 37 | Default: 1 38 | Description: The priority for the routing rule added to the load balancer. 39 | This only applies if you have multiple services which have been 40 | assigned to different paths on the load balancer. 41 | DesiredCount: 42 | Type: Number 43 | Default: 2 44 | Description: How many copies of the service task to run 45 | Role: 46 | Type: String 47 | Default: "" 48 | Description: (Optional) An IAM role to give the service's containers if the code within needs to 49 | access other AWS resources like S3 buckets, DynamoDB tables, etc 50 | 51 | Conditions: 52 | HasCustomRole: !Not [ !Equals [!Ref 'Role', ''] ] 53 | 54 | Resources: 55 | # A log group for storing the stdout logs from this service's containers 56 | LogGroup: 57 | Type: AWS::Logs::LogGroup 58 | Properties: 59 | LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName} 60 | 61 | # The task definition. This is a simple metadata description of what 62 | # container to run, and what resource requirements it has. 63 | TaskDefinition: 64 | Type: AWS::ECS::TaskDefinition 65 | Properties: 66 | Family: !Sub ${EnvironmentName}-${ServiceName} 67 | Cpu: !Ref 'ContainerCpu' 68 | Memory: !Ref 'ContainerMemory' 69 | NetworkMode: awsvpc 70 | RequiresCompatibilities: 71 | - FARGATE 72 | ExecutionRoleArn: 73 | Fn::ImportValue: !Sub ${EnvironmentName}:ECSTaskExecutionRole 74 | TaskRoleArn: 75 | Fn::If: 76 | - 'HasCustomRole' 77 | - !Ref 'Role' 78 | - !Ref "AWS::NoValue" 79 | ContainerDefinitions: 80 | - Name: !Ref 'ServiceName' 81 | Cpu: !Ref 'ContainerCpu' 82 | Memory: !Ref 'ContainerMemory' 83 | Image: !Ref 'ImageUrl' 84 | PortMappings: 85 | - ContainerPort: !Ref 'ContainerPort' 86 | LogConfiguration: 87 | LogDriver: 'awslogs' 88 | Options: 89 | awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName} 90 | awslogs-region: !Ref 'AWS::Region' 91 | awslogs-stream-prefix: !Ref 'ServiceName' 92 | 93 | # The service. The service is a resource which allows you to run multiple 94 | # copies of a type of task, and gather up their logs and metrics, as well 95 | # as monitor the number of running tasks and replace any that have crashed 96 | Service: 97 | Type: AWS::ECS::Service 98 | DependsOn: LoadBalancerRule 99 | Properties: 100 | ServiceName: !Sub ${EnvironmentName}-${ServiceName} 101 | Cluster: 102 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 103 | LaunchType: FARGATE 104 | DeploymentConfiguration: 105 | MaximumPercent: 200 106 | MinimumHealthyPercent: 75 107 | DesiredCount: !Ref 'DesiredCount' 108 | NetworkConfiguration: 109 | AwsvpcConfiguration: 110 | AssignPublicIp: ENABLED 111 | SecurityGroups: 112 | - Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup 113 | Subnets: 114 | - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetOne 115 | - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetTwo 116 | TaskDefinition: !Ref 'TaskDefinition' 117 | LoadBalancers: 118 | - ContainerName: !Ref 'ServiceName' 119 | ContainerPort: !Ref 'ContainerPort' 120 | TargetGroupArn: !Ref 'TargetGroup' 121 | 122 | # A target group. This is used for keeping track of all the tasks, and 123 | # what IP addresses / port numbers they have. You can query it yourself, 124 | # to use the addresses yourself, but most often this target group is just 125 | # connected to an application load balancer, or network load balancer, so 126 | # it can automatically distribute traffic across all the targets. 127 | TargetGroup: 128 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 129 | Properties: 130 | HealthCheckIntervalSeconds: 5 131 | HealthCheckPath: / 132 | HealthCheckProtocol: HTTP 133 | HealthCheckTimeoutSeconds: 4 134 | HealthyThresholdCount: 2 135 | TargetType: ip 136 | Port: !Ref 'ContainerPort' 137 | Protocol: HTTP 138 | UnhealthyThresholdCount: 3 139 | VpcId: 140 | Fn::ImportValue: !Sub ${EnvironmentName}:VpcId 141 | TargetGroupAttributes: 142 | - Key: 'deregistration_delay.timeout_seconds' 143 | Value: 5 144 | 145 | # Create a rule on the load balancer for routing traffic to the target group 146 | LoadBalancerRule: 147 | Type: AWS::ElasticLoadBalancingV2::ListenerRule 148 | Properties: 149 | Actions: 150 | - TargetGroupArn: !Ref 'TargetGroup' 151 | Type: 'forward' 152 | Conditions: 153 | - Field: path-pattern 154 | Values: [!Ref 'LoadBalancerPath'] 155 | ListenerArn: 156 | Fn::ImportValue: !Sub ${EnvironmentName}:PublicListener 157 | Priority: !Ref 'LoadBalancerPriority' 158 | 159 | Outputs: 160 | ServiceURL: 161 | Value: 162 | Fn::Join: 163 | - "" 164 | - - Fn::ImportValue: !Sub ${EnvironmentName}:ExternalUrl 165 | - !Join [ "", !Split [ "*" , !Ref LoadBalancerPath ] ] 166 | -------------------------------------------------------------------------------- /cloudformation-templates/setup.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Description: Set up IAM roles for GitHub Actions, CloudFormation, and ECS 3 | 4 | Parameters: 5 | GitHubOrg: 6 | Description: Name of your GitHub organization/user (case sensitive) 7 | Type: String 8 | RepositoryName: 9 | Description: Name of your GitHub repository (case sensitive) 10 | Type: String 11 | OIDCProviderArn: 12 | Description: ARN of your GitHub OIDC Provider, if it exists already in your AWS account. 13 | Default: "" 14 | Type: String 15 | OIDCAudience: 16 | Description: Audience supplied to the configure-aws-credentials GitHub Action. 17 | Default: "sts.amazonaws.com" 18 | Type: String 19 | GitHubActionsRoleName: 20 | Description: The name of the role that GitHub Actions will use to deploy CloudFormation stacks. 21 | Type: String 22 | Default: github-actions-cloudformation-deploy-role 23 | CloudFormationDeploymentRoleName: 24 | Description: The name of the role that CloudFormation will use to deploy the resources in stacks. 25 | Type: String 26 | Default: github-actions-cloudformation-stack-role 27 | ECSTaskExecutionRoleName: 28 | Type: String 29 | Description: The name of the role that ECS will use to run tasks. 30 | Default: github-actions-ecs-task-execution-role 31 | 32 | Conditions: 33 | CreateOIDCProvider: !Equals 34 | - !Ref OIDCProviderArn 35 | - "" 36 | 37 | Resources: 38 | GitHubActionsOidc: 39 | Type: AWS::IAM::OIDCProvider 40 | Condition: CreateOIDCProvider 41 | Properties: 42 | Url: https://token.actions.githubusercontent.com 43 | ClientIdList: 44 | - sts.amazonaws.com 45 | ThumbprintList: 46 | - ffffffffffffffffffffffffffffffffffffffff 47 | 48 | GitHubActionsRole: 49 | Type: AWS::IAM::Role 50 | Properties: 51 | RoleName: !Ref GitHubActionsRoleName 52 | AssumeRolePolicyDocument: 53 | Statement: 54 | - Effect: Allow 55 | Action: sts:AssumeRoleWithWebIdentity 56 | Principal: 57 | Federated: !If 58 | - CreateOIDCProvider 59 | - !Ref GitHubActionsOidc 60 | - !Ref OIDCProviderArn 61 | Condition: 62 | StringEquals: 63 | token.actions.githubusercontent.com:aud: !Ref OIDCAudience 64 | StringLike: 65 | token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}/${RepositoryName}:* 66 | 67 | # The permissions that the GitHub Actions deployment workflow will have 68 | GitHubActionsPolicy: 69 | Type: AWS::IAM::RolePolicy 70 | Properties: 71 | RoleName: !Ref GitHubActionsRole 72 | PolicyName: allow-github-actions-cloudformation-deploy 73 | PolicyDocument: 74 | Version: "2012-10-17" 75 | Statement: 76 | - Action: 77 | - "cloudformation:*" 78 | - "ec2:DescribeVpcs" 79 | - "ec2:DescribeSubnets" 80 | - "ecr:GetAuthorizationToken" 81 | Effect: Allow 82 | Resource: "*" 83 | - Action: "cloudformation:DeleteStack" 84 | Effect: Deny 85 | Resource: "*" 86 | - Action: "iam:PassRole" 87 | Effect: Allow 88 | Resource: !GetAtt CloudFormationDeploymentRole.Arn 89 | - Action: 90 | - "ecr:GetDownloadUrlForLayer" 91 | - "ecr:BatchGetImage" 92 | - "ecr:BatchCheckLayerAvailability" 93 | - "ecr:PutImage" 94 | - "ecr:InitiateLayerUpload" 95 | - "ecr:UploadLayerPart" 96 | - "ecr:CompleteLayerUpload" 97 | Effect: Allow 98 | Resource: !Sub "arn:${AWS::Partition}:ecr:*:${AWS::AccountId}:repository/github-actions-*" 99 | 100 | # The permissions that the CloudFormation stack deployments will have 101 | CloudFormationDeploymentRole: 102 | Type: AWS::IAM::Role 103 | Properties: 104 | RoleName: !Ref CloudFormationDeploymentRoleName 105 | AssumeRolePolicyDocument: 106 | Version: "2012-10-17" 107 | Statement: 108 | - Effect: "Allow" 109 | Principal: 110 | Service: 111 | - cloudformation.amazonaws.com 112 | Action: 113 | - "sts:AssumeRole" 114 | ManagedPolicyArns: 115 | - arn:aws:iam::aws:policy/AmazonVPCFullAccess 116 | - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess 117 | - arn:aws:iam::aws:policy/AmazonECS_FullAccess 118 | - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess 119 | - arn:aws:iam::aws:policy/ElasticLoadBalancingFullAccess 120 | Policies: 121 | - PolicyName: allow-pass-role-for-ecs-task-exec-role 122 | PolicyDocument: 123 | Version: "2012-10-17" 124 | Statement: 125 | - Action: "iam:PassRole" 126 | Effect: Allow 127 | Resource: !GetAtt ECSTaskExecutionRole.Arn 128 | 129 | # This is a role used to start and manage ECS tasks (pull the container image, etc) 130 | ECSTaskExecutionRole: 131 | Type: AWS::IAM::Role 132 | Properties: 133 | RoleName: !Ref ECSTaskExecutionRoleName 134 | AssumeRolePolicyDocument: 135 | Statement: 136 | - Effect: Allow 137 | Principal: 138 | Service: [ecs-tasks.amazonaws.com] 139 | Action: ['sts:AssumeRole'] 140 | Path: / 141 | ManagedPolicyArns: 142 | - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy 143 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |