├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── db-cluster.yaml ├── docs ├── aws-nextcloud.png └── cw-dashboard.png ├── ecs-nextcloud.yml └── simplevpc.yaml /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | build/ 3 | output/ 4 | .DS_Store 5 | packaged.yaml 6 | -------------------------------------------------------------------------------- /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 *main* 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 | -------------------------------------------------------------------------------- /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 | # Nextcloud Container Deployment on AWS - Serverless 2 | 3 | ## Welcome 4 | 5 | This repository provides AWS CloudFormation templates to deploy NextCloud on AWS completely. 6 | No need to manage servers or manually react to monitoring events with adding capacity manually. 7 | 8 | ## AWS Services & How they match the NextCloud design 9 | 10 | * Elastic Container Service - Fargate 11 | * Running and scaling the official nextcloud docker container (Apache, PHP) 12 | * Elastic Filesystem - NFS 13 | * Persist basic settings and configuration, support official nextcloud upgrade mechanism 14 | * Amazon S3 15 | * Primary data storage for cloud native data handling (archiving, tiering, versioning) 16 | * RDS Aurora Serverless - RDS (Postgres) 17 | * Cloud native full managed auto-scaled database system backing the nextcloud installation 18 | * ElastiCache - Redis 19 | * Handles PHP Sessions to enable container cluster to scale easily without interruption for end-users 20 | * Application Load Balancer, Route53, Amazon Certificate Manager 21 | * Secures the application with HTTPS, balances load, performs health checks, auto-certificate renewal 22 | 23 | ## Quickstart 24 | 25 | You can use the following link to deploy this solution directly into your AWS account. Ensure you are logged into the AWS Console before following it. 26 | 27 | [Quickstart CloudFormation Link](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?templateURL=https:%2F%2Fs3.amazonaws.com%2Ff7o-quickstart%2Faws-serverless-nextcloud%2Fecs-nextcloud.yml) 28 | 29 | ## Deployment 30 | 31 | This project assumes a familiarity with AWS CLI and AWS CloudFormation. Additional reference can be found at the following links: 32 | [https://aws.amazon.com/cli/](https://aws.amazon.com/cli/) 33 | [https://aws.amazon.com/cloudformation/](https://aws.amazon.com/cloudformation/) 34 | 35 | * If you have never used AWS ECS within your AWS Account before, create the Service-Linked Role before going on. See the official [AWS Documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using-service-linked-roles.html#create-service-linked-role) 36 | * `aws cloudformation package --template-file ecs-nextcloud.yml --s3-bucket --output-template-file packaged.yaml ` 37 | * Deploy packaged CloudFormation file (`packaged.yaml`) with appropriate parameters 38 | * For example using `aws cloudformation deploy --template-file packaged.yaml --stack-name nextcloud-test-env --parameter-overrides DbPassword= NextCloudAdminPassword= IsolationLevel=Public --capabilities CAPABILITY_IAM` 39 | * The VPC setup has three levels of isolation 40 | * `Public` places containers within the public subnets 41 | * `Private` places containers into private subnets, deploys one NAT Gateway for outbound internet access 42 | * `PrivateHA` same as privat, but deploys two NAT Gateways 43 | * After the CloudFormation Stack is deployed successfully it takes up to 15min for the frontend being available initially 44 | * Find the Nextcloud URLs within the CloudFormation Outputs 45 | 46 | ## Architecture 47 | 48 | ![Architecture Diagram](docs/aws-nextcloud.png) 49 | 50 | ## Sizing 51 | 52 | The recommendation is to use the at least the default values to get decent performance (`cpu: 1024, mem: 2048`). 53 | A desired container capacity of 2 allows scaling and re-deployment without downtime. For initialization go with a single container to avoid clashes when copying files during the setup phase. 54 | 55 | The baseline cost drivers are the AWS RDS database, ElastiCache Redis and Fargate task costs. Find the hourly charged costs for your AWS region within the AWS pricing pages: 56 | * Check per ACU (Auora Capacity Unit) costs https://aws.amazon.com/rds/aurora/pricing/ 57 | * Fargate Pricing https://aws.amazon.com/fargate/pricing/ 58 | * ElastiCache Redis Pricing https://aws.amazon.com/elasticache/pricing/ 59 | 60 | ## How to upgrade Nextcloud to newer version 61 | 62 | 1. Create backups of RDS and EFS 63 | 2. Suspend AutoScaling using the CloudFormation parameter 64 | 3. Scale in to 1 task (set desired ECS capacity to 1) 65 | 4. Update CFN stack with new version number 66 | 5. Wait for Nextcloud to become available 67 | 6. Verify the upgrade was successful 68 | 7. Scale out service to desired size and disable AutoScaling suspension 69 | 70 | ## Future Work 71 | 72 | * Enhanced monitoring for fine granular auto-scaling (ECS) 73 | * Enable WebCron 74 | * Whenever Nextcloud supports it 75 | * Use short-term credentials instead of IAM User for S3 access 76 | * Redis Cluster Support 77 | 78 | ## Monitoring 79 | 80 | Sample CloudWatch Dashboard pre-configured with basic metrics will be deployed within CloudFormation 81 | 82 | ![CW-Dashboard](docs/cw-dashboard.png) 83 | 84 | ### Note: 85 | 86 | * **While code samples in this repository has been tested and believe it works well, as always, be sure to test it in your environment before using it in production!** 87 | * It is highly recommended to change the administrator password after initial deployment 88 | * While the `Public` VPC isolation level is the most cost efficient one, the suggestion is, for production workloads keep instances and containers in private subnets. This lowers risk, e.g. opening up ports with wrongly configured inbound port on VPC security groups. 89 | 90 | ## Security 91 | 92 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 93 | 94 | ## License 95 | 96 | This library is licensed under the MIT-0 License. See the [LICENSE](LICENSE) file. 97 | 98 | **This deployment references the official [Nextcloud Docker image](https://github.com/nextcloud/docker) which is published under [AGPL-3.0 License](https://github.com/nextcloud/docker/blob/master/LICENSE.md).** 99 | -------------------------------------------------------------------------------- /db-cluster.yaml: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | Description: Aurora Serverless DB 18 | 19 | Parameters: 20 | DbUserName: 21 | Type: String 22 | DbPassword: 23 | Type: String 24 | NoEcho: True 25 | DbName: 26 | Type: String 27 | DbMinCapacity: 28 | Type: Number 29 | DbMaxCapacity: 30 | Type: Number 31 | PrivateSubnets: 32 | Type: String 33 | VpcId: 34 | Type: String 35 | FargateSecGroupId: 36 | Type: String 37 | 38 | Resources: 39 | RdsSubnetGroup: 40 | Type: "AWS::RDS::DBSubnetGroup" 41 | Properties: 42 | DBSubnetGroupDescription: description 43 | SubnetIds: !Split [',', !Ref PrivateSubnets] 44 | PostgresRdsSecurityGroup: 45 | Type: 'AWS::EC2::SecurityGroup' 46 | Properties: 47 | GroupDescription: ECS Security Group 48 | VpcId: !Ref VpcId 49 | PostgresRdsSecurityGroupPostgresInboundFargate: 50 | Type: 'AWS::EC2::SecurityGroupIngress' 51 | Properties: 52 | GroupId: !Ref PostgresRdsSecurityGroup 53 | IpProtocol: tcp 54 | FromPort: '5432' 55 | ToPort: '5432' 56 | SourceSecurityGroupId: !Ref FargateSecGroupId 57 | PostgresRdsCluster: 58 | Type: "AWS::RDS::DBCluster" 59 | Properties: 60 | DBClusterParameterGroupName: 61 | Ref: PostgresRdsClusterParameterGroup 62 | DBSubnetGroupName: 63 | Ref: RdsSubnetGroup 64 | DatabaseName: !Ref DbName 65 | Engine: aurora-postgresql 66 | EngineMode: serverless 67 | EngineVersion: 10.2 68 | Port: 5432 69 | StorageEncrypted: true 70 | ScalingConfiguration: 71 | AutoPause: true 72 | MinCapacity: !Ref DbMinCapacity 73 | MaxCapacity: !Ref DbMaxCapacity 74 | SecondsUntilAutoPause: 3600 75 | VpcSecurityGroupIds: 76 | - !Ref PostgresRdsSecurityGroup 77 | MasterUserPassword: 78 | Ref: DbPassword 79 | MasterUsername: 80 | Ref: DbUserName 81 | PostgresRdsClusterParameterGroup: 82 | Type: "AWS::RDS::DBClusterParameterGroup" 83 | Properties: 84 | Description: "CloudFormation Aurora Cluster Parameter Group" 85 | Family: aurora-postgresql10 86 | Parameters: 87 | timezone: UTC 88 | 89 | Outputs: 90 | DBIdentifier: 91 | Value: !Ref PostgresRdsCluster 92 | EndpointUrl: 93 | Value: !GetAtt PostgresRdsCluster.Endpoint.Address 94 | EndpointPort: 95 | Value: !GetAtt PostgresRdsCluster.Endpoint.Port 96 | 97 | -------------------------------------------------------------------------------- /docs/aws-nextcloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-nextcloud/cdd19bc9035777157ce3252d62c7df69a57a9845/docs/aws-nextcloud.png -------------------------------------------------------------------------------- /docs/cw-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-nextcloud/cdd19bc9035777157ce3252d62c7df69a57a9845/docs/cw-dashboard.png -------------------------------------------------------------------------------- /ecs-nextcloud.yml: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | AWSTemplateFormatVersion: 2010-09-09 18 | Description: >- 19 | Amazon ECS Service for Nextcloud with Serverless RDS, EFS & S3 20 | 21 | Metadata: 22 | AWS::CloudFormation::Interface: 23 | ParameterGroups: 24 | - Label: 25 | default: 'Deployment Identifiers' 26 | Parameters: 27 | - DeploymentName 28 | - Label: 29 | default: 'Nextcloud Configuration' 30 | Parameters: 31 | - NextCloudAdminUser 32 | - NextCloudAdminPassword 33 | - NextCloudVersion 34 | - Label: 35 | default: 'Database Configuration' 36 | Parameters: 37 | - DbUserName 38 | - DbPassword 39 | - NextCloudDbName 40 | - DbMinCapacity 41 | - DbMaxCapacity 42 | - Label: 43 | default: 'AWS ECS Configuration' 44 | Parameters: 45 | - EcsTaskCpu 46 | - EcsTaskMem 47 | - EcsCapacityProvider 48 | - EcsMinCapacity 49 | - EcsInitialDesiredCapacity 50 | - EcsMaxCapacity 51 | - EcsTargetCpuUtilization 52 | - SuspendAutoScaling 53 | - Label: 54 | default: 'ElastiCache Redis Configuration' 55 | Parameters: 56 | - RedisTshirtSize 57 | - Label: 58 | default: 'VPC Configuration' 59 | Parameters: 60 | - IsolationLevel 61 | - Label: 62 | default: 'Custom Domain Configuration (optional)' 63 | Parameters: 64 | - Domain 65 | - Route53Zone 66 | 67 | Parameters: 68 | DeploymentName: 69 | Description: Deployment Name (Unique name, no punctuation) 70 | Type: String 71 | Default: nc-serverless 72 | Route53Zone: 73 | Description: Route53 Public Hosted Zone ID to configure custom domain 74 | Type: String 75 | Default: "" 76 | Domain: 77 | Description: Speficy a full quallified domain name to use with this Nextcloud deployment 78 | Type: String 79 | Default: "" 80 | DbUserName: 81 | Description: Database Master Username 82 | Type: String 83 | AllowedPattern: ^[a-zA-Z0-9]{6,32} 84 | Default: nextcloud 85 | DbPassword: 86 | Description: Database Master Password 87 | Type: String 88 | NoEcho: True 89 | DbMinCapacity: 90 | Description: Minimal Aurora Capacity Units Provisioned 91 | Type: Number 92 | MinValue: 2 93 | MaxValue: 384 94 | Default: 2 95 | DbMaxCapacity: 96 | Description: Maxmimal Aurora Capacity Units Provisioned 97 | Type: Number 98 | MinValue: 2 99 | MaxValue: 384 100 | Default: 8 101 | NextCloudAdminUser: 102 | Description: Initial Nextcloud Admin User 103 | Type: String 104 | AllowedPattern: ^[a-zA-Z0-9]{6,32} 105 | Default: ncadmin 106 | NextCloudAdminPassword: 107 | Description: Initial Nextcloud Admin Password (change after first login) 108 | Type: String 109 | NoEcho: True 110 | NextCloudDbName: 111 | Description: Nextcloud Database Name 112 | Type: String 113 | AllowedPattern: ^[a-zA-Z0-9]{6,32} 114 | Default: nextcloud 115 | NextCloudVersion: 116 | Description: Nextcloud Version to be deployed (no downgrade possible) 117 | Type: String 118 | Default: 23.0.1 119 | S3SecretRotationSerial: 120 | Description: In order to rotate long-term S3 credentials increase by 1 121 | Type: Number 122 | Default: 1 123 | IsolationLevel: 124 | Description: VPC Isolation Level (see README.md) 125 | Type: String 126 | Default: Private 127 | AllowedValues: 128 | - PrivateHA 129 | - Private 130 | - Public 131 | EcsCapacityProvider: 132 | Description: Optional select Spot Fargate Tasks for Development Environments 133 | Type: String 134 | Default: FARGATE 135 | AllowedValues: 136 | - FARGATE_SPOT 137 | - FARGATE 138 | EcsTaskCpu: 139 | Description: CPU Units to provision per ECS Container 140 | Type: Number 141 | Default: 1024 142 | AllowedValues: 143 | - 256 144 | - 512 145 | - 1024 146 | - 2048 147 | - 4096 148 | EcsTaskMem: 149 | Description: Memory to provision per ECS Container (MB) 150 | Type: Number 151 | Default: 2048 152 | EcsMinCapacity: 153 | Description: Minimum number of ECS Containers to provision 154 | Type: Number 155 | Default: 1 156 | EcsInitialDesiredCapacity: 157 | Description: Initial Desired number of ECS Containers to provision 158 | Type: Number 159 | Default: 1 160 | EcsMaxCapacity: 161 | Description: Maximum number of ECS Containers to provision 162 | Type: Number 163 | Default: 25 164 | EcsTargetCpuUtilization: 165 | Description: Threshold to be reached for scaling in/out 166 | Type: Number 167 | Default: 50 168 | SuspendAutoScaling: 169 | Description: Suspend Auto Scaling for Maintanence 170 | Type: String 171 | Default: false 172 | RedisTshirtSize: 173 | Description: ElasticCache Redis Node Size 174 | Type: String 175 | Default: cache.t3.small 176 | AllowedValues: 177 | - cache.t3.micro 178 | - cache.t3.small 179 | - cache.t3.medium 180 | - cache.m5.large 181 | - cache.m5.xlarge 182 | - cache.m5.2xlarge 183 | - cache.m5.4xlarge 184 | - cache.m5.12xlarge 185 | - cache.m5.24xlarge 186 | - cache.r5.large 187 | - cache.r5.xlarge 188 | - cache.r5.2xlarge 189 | - cache.r5.4xlarge 190 | - cache.r5.12xlarge 191 | - cache.r5.24xlarge 192 | 193 | Mappings: 194 | Config: 195 | Container: 196 | Uid: 33 197 | Gid: 0 198 | Permission: "0777" 199 | 200 | Conditions: 201 | CustomDomain: !Not [!And [!Equals [!Ref Domain, ""], !Equals [!Ref Route53Zone, ""]]] 202 | PrivateSubnets: !Not [!Equals [!Ref IsolationLevel, Public]] 203 | 204 | Resources: 205 | VpcStack: 206 | Type: AWS::CloudFormation::Stack 207 | Properties: 208 | TemplateURL: ./simplevpc.yaml 209 | TimeoutInMinutes: 60 210 | Parameters: 211 | EnvironmentName: !Ref DeploymentName 212 | IsolationLevel: !Ref IsolationLevel 213 | 214 | RdsStack: 215 | Type: AWS::CloudFormation::Stack 216 | Properties: 217 | TemplateURL: ./db-cluster.yaml 218 | TimeoutInMinutes: 60 219 | Parameters: 220 | DbUserName: !Ref DbUserName 221 | DbPassword: !Ref DbPassword 222 | DbName: nextcloud 223 | DbMinCapacity: !Ref DbMinCapacity 224 | DbMaxCapacity: !Ref DbMaxCapacity 225 | PrivateSubnets: !GetAtt VpcStack.Outputs.PrivateSubnets 226 | VpcId: !GetAtt VpcStack.Outputs.VPC 227 | FargateSecGroupId: !Ref EcsSecurityGroup 228 | 229 | EcsCluster: 230 | Type: 'AWS::ECS::Cluster' 231 | Properties: 232 | CapacityProviders: 233 | - FARGATE 234 | - FARGATE_SPOT 235 | Configuration: 236 | ExecuteCommandConfiguration: 237 | Logging: DEFAULT 238 | 239 | EcsSecurityGroup: 240 | Type: 'AWS::EC2::SecurityGroup' 241 | Properties: 242 | GroupDescription: ECS Security Group 243 | VpcId: !GetAtt VpcStack.Outputs.VPC 244 | 245 | EcsSecurityGroupHTTPinbound: 246 | Type: 'AWS::EC2::SecurityGroupIngress' 247 | Properties: 248 | GroupId: !Ref EcsSecurityGroup 249 | IpProtocol: tcp 250 | FromPort: '80' 251 | ToPort: '80' 252 | SourceSecurityGroupId: !Ref ElbSecurityGroup 253 | 254 | CloudwatchLogsGroup: 255 | Type: 'AWS::Logs::LogGroup' 256 | Properties: 257 | LogGroupName: !Join 258 | - '-' 259 | - - !Sub ${DeploymentName} 260 | - !Ref 'AWS::StackName' 261 | RetentionInDays: 14 262 | DataBucket: 263 | DeletionPolicy: Retain 264 | Type: 'AWS::S3::Bucket' 265 | Properties: 266 | VersioningConfiguration: 267 | Status: Enabled 268 | BucketEncryption: 269 | ServerSideEncryptionConfiguration: 270 | - BucketKeyEnabled: true 271 | ServerSideEncryptionByDefault: 272 | SSEAlgorithm: AES256 273 | BucketUser: 274 | Type: AWS::IAM::User 275 | Properties: 276 | Policies: 277 | - PolicyName: s3-access 278 | PolicyDocument: 279 | Statement: 280 | - Effect: Allow 281 | Action: 282 | - s3:* 283 | Resource: !Sub arn:aws:s3:::${DataBucket} 284 | - Effect: Allow 285 | Action: 286 | - s3:* 287 | Resource: !Sub arn:aws:s3:::${DataBucket}/* 288 | - Effect: Deny 289 | Action: 290 | - s3:DeleteBucket* 291 | - s3:PutBucketPolicy 292 | - s3:PutEncryptionConfiguration 293 | Resource: "*" 294 | - Effect: Allow 295 | Action: 296 | - s3:GetBucketLocation 297 | Resource: arn:aws:s3:::* 298 | BucketUserCredentials: 299 | Type: AWS::IAM::AccessKey 300 | Properties: 301 | Serial: !Ref S3SecretRotationSerial 302 | Status: Active 303 | UserName: !Ref BucketUser 304 | 305 | Efs: 306 | Type: AWS::EFS::FileSystem 307 | Properties: 308 | Encrypted: true 309 | EfsMountTarget1: 310 | Type: AWS::EFS::MountTarget 311 | Properties: 312 | FileSystemId: 313 | Ref: Efs 314 | SubnetId: !Select [0, !Split [ ',', !GetAtt VpcStack.Outputs.PrivateSubnets ]] 315 | SecurityGroups: 316 | - Ref: "EfsSecurityGroup" 317 | EfsMountTarget2: 318 | Type: AWS::EFS::MountTarget 319 | Properties: 320 | FileSystemId: 321 | Ref: Efs 322 | SubnetId: !Select [ 1, !Split [ ',', !GetAtt VpcStack.Outputs.PrivateSubnets ] ] 323 | SecurityGroups: 324 | - Ref: "EfsSecurityGroup" 325 | EfsAPNextcloud: 326 | Type: AWS::EFS::AccessPoint 327 | Properties: 328 | FileSystemId: !Ref Efs 329 | RootDirectory: 330 | Path: !Sub /${DeploymentName}/nextcloud 331 | CreationInfo: 332 | OwnerUid: !FindInMap [Config, Container, Uid] 333 | OwnerGid: !FindInMap [Config, Container, Gid] 334 | Permissions: !FindInMap [Config, Container, Permission] 335 | EfsAPConfig: 336 | Type: AWS::EFS::AccessPoint 337 | Properties: 338 | FileSystemId: !Ref Efs 339 | RootDirectory: 340 | Path: !Sub /${DeploymentName}/config 341 | CreationInfo: 342 | OwnerUid: !FindInMap [Config, Container, Uid] 343 | OwnerGid: !FindInMap [Config, Container, Gid] 344 | Permissions: !FindInMap [Config, Container, Permission] 345 | EfsAPApps: 346 | Type: AWS::EFS::AccessPoint 347 | Properties: 348 | FileSystemId: !Ref Efs 349 | RootDirectory: 350 | Path: !Sub /${DeploymentName}/apps 351 | CreationInfo: 352 | OwnerUid: !FindInMap [Config, Container, Uid] 353 | OwnerGid: !FindInMap [Config, Container, Gid] 354 | Permissions: !FindInMap [Config, Container, Permission] 355 | EfsAPData: 356 | Type: AWS::EFS::AccessPoint 357 | Properties: 358 | FileSystemId: !Ref Efs 359 | RootDirectory: 360 | Path: !Sub /${DeploymentName}/data 361 | CreationInfo: 362 | OwnerUid: !FindInMap [Config, Container, Uid] 363 | OwnerGid: !FindInMap [Config, Container, Gid] 364 | Permissions: !FindInMap [Config, Container, Permission] 365 | EfsSecurityGroup: 366 | Type: 'AWS::EC2::SecurityGroup' 367 | Properties: 368 | GroupDescription: ECS Security Group 369 | VpcId: !GetAtt VpcStack.Outputs.VPC 370 | 371 | EfsSecurityGroupNFSinbound: 372 | Type: 'AWS::EC2::SecurityGroupIngress' 373 | Properties: 374 | GroupId: !Ref EfsSecurityGroup 375 | IpProtocol: tcp 376 | FromPort: '2049' 377 | ToPort: '2049' 378 | SourceSecurityGroupId: !Ref EcsSecurityGroup 379 | # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/bind-mounts.html 380 | EcsTaskDefinition: 381 | Type: 'AWS::ECS::TaskDefinition' 382 | Properties: 383 | Family: !Join 384 | - '' 385 | - - !Ref 'AWS::StackName' 386 | - '-ecs-nextcloud' 387 | NetworkMode: awsvpc 388 | RequiresCompatibilities: 389 | - "FARGATE" 390 | ExecutionRoleArn: !GetAtt ECSTaskExecRole.Arn 391 | TaskRoleArn: !GetAtt ECSTaskRole.Arn 392 | Cpu: !Ref EcsTaskCpu 393 | Memory: !Ref EcsTaskMem 394 | ContainerDefinitions: 395 | - Name: nextcloud 396 | LogConfiguration: 397 | LogDriver: awslogs 398 | Options: 399 | awslogs-group: !Ref CloudwatchLogsGroup 400 | awslogs-region: !Ref AWS::Region 401 | awslogs-stream-prefix: nextcloud 402 | Environment: 403 | - Name: POSTGRES_DB 404 | Value: !Ref NextCloudDbName 405 | - Name: POSTGRES_USER 406 | Value: !Ref DbUserName 407 | - Name: POSTGRES_PASSWORD 408 | Value: !Ref DbPassword 409 | - Name: POSTGRES_HOST 410 | Value: !GetAtt RdsStack.Outputs.EndpointUrl 411 | - Name: NEXTCLOUD_TRUSTED_DOMAINS 412 | Value: 413 | Fn::Sub: 414 | - ${Domain} ${ElbDomain} 415 | - ElbDomain: !GetAtt ElasticLoadBalancer.DNSName 416 | - Name: NEXTCLOUD_ADMIN_USER 417 | Value: !Ref NextCloudAdminUser 418 | - Name: NEXTCLOUD_ADMIN_PASSWORD 419 | Value: !Ref NextCloudAdminPassword 420 | - Name: OBJECTSTORE_S3_BUCKET 421 | Value: !Ref DataBucket 422 | - Name: OBJECTSTORE_S3_REGION 423 | Value: !Ref AWS::Region 424 | - Name: OBJECTSTORE_S3_KEY 425 | Value: !Ref BucketUserCredentials 426 | - Name: OBJECTSTORE_S3_SECRET 427 | Value: !GetAtt BucketUserCredentials.SecretAccessKey 428 | - Name: OVERWRITEPROTOCOL 429 | Value: !If [CustomDomain, https, http] 430 | - Name: REDIS_HOST 431 | Value: !GetAtt RedisReplicationGroup.PrimaryEndPoint.Address 432 | - Name: REDIS_PORT 433 | Value: !GetAtt RedisReplicationGroup.PrimaryEndPoint.Port 434 | PortMappings: 435 | - HostPort: 80 436 | Protocol: tcp 437 | ContainerPort: 80 438 | MountPoints: 439 | - ContainerPath: "/var/www/html" 440 | SourceVolume: nextcloud 441 | - ContainerPath: "/var/www/html/custom_apps" 442 | SourceVolume: apps 443 | - ContainerPath: "/var/www/html/config" 444 | SourceVolume: config 445 | - ContainerPath: "/var/www/html/data" 446 | SourceVolume: data 447 | Image: !Sub nextcloud:${NextCloudVersion}-apache 448 | Essential: true 449 | Volumes: 450 | - Name: nextcloud 451 | EFSVolumeConfiguration: 452 | FilesystemId: !Ref Efs 453 | AuthorizationConfig: 454 | AccessPointId: !Ref EfsAPNextcloud 455 | IAM: ENABLED 456 | TransitEncryption: ENABLED 457 | - Name: apps 458 | EFSVolumeConfiguration: 459 | FilesystemId: !Ref Efs 460 | AuthorizationConfig: 461 | AccessPointId: !Ref EfsAPApps 462 | IAM: ENABLED 463 | TransitEncryption: ENABLED 464 | - Name: config 465 | EFSVolumeConfiguration: 466 | FilesystemId: !Ref Efs 467 | AuthorizationConfig: 468 | AccessPointId: !Ref EfsAPConfig 469 | IAM: ENABLED 470 | TransitEncryption: ENABLED 471 | - Name: data 472 | EFSVolumeConfiguration: 473 | FilesystemId: !Ref Efs 474 | AuthorizationConfig: 475 | AccessPointId: !Ref EfsAPData 476 | IAM: ENABLED 477 | TransitEncryption: ENABLED 478 | AlbCertificate: 479 | Condition: CustomDomain 480 | Type: AWS::CertificateManager::Certificate 481 | Properties: 482 | DomainName: !Ref Domain 483 | ValidationMethod: DNS 484 | DomainValidationOptions: 485 | - DomainName: !Ref Domain 486 | HostedZoneId: !Ref Route53Zone 487 | ElbSecurityGroup: 488 | Type: 'AWS::EC2::SecurityGroup' 489 | Properties: 490 | GroupDescription: ELB Security Group 491 | VpcId: !GetAtt VpcStack.Outputs.VPC 492 | 493 | ElbSecurityGroupHTTPSinbound: 494 | Type: 'AWS::EC2::SecurityGroupIngress' 495 | Properties: 496 | GroupId: !Ref ElbSecurityGroup 497 | IpProtocol: tcp 498 | FromPort: '443' 499 | ToPort: '443' 500 | CidrIp: 0.0.0.0/0 501 | ElbSecurityGroupHTTPinbound: 502 | Type: 'AWS::EC2::SecurityGroupIngress' 503 | Properties: 504 | GroupId: !Ref ElbSecurityGroup 505 | IpProtocol: tcp 506 | FromPort: '80' 507 | ToPort: '80' 508 | CidrIp: 0.0.0.0/0 509 | ElasticLoadBalancer: 510 | Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer' 511 | Properties: 512 | Scheme: internet-facing 513 | LoadBalancerAttributes: 514 | - Key: idle_timeout.timeout_seconds 515 | Value: '30' 516 | Subnets: !Split [ ',', !GetAtt VpcStack.Outputs.PublicSubnets ] 517 | SecurityGroups: 518 | - !Ref ElbSecurityGroup 519 | Route53AliasRecord: 520 | Condition: CustomDomain 521 | Type: AWS::Route53::RecordSet 522 | Properties: 523 | AliasTarget: 524 | DNSName: !GetAtt ElasticLoadBalancer.DNSName 525 | EvaluateTargetHealth: true 526 | HostedZoneId: !GetAtt ElasticLoadBalancer.CanonicalHostedZoneID 527 | Comment: Sso Api Gateway 528 | HostedZoneId: !Ref Route53Zone 529 | Name: !Ref Domain 530 | Type: A 531 | 532 | LoadBalancerListener: 533 | Type: 'AWS::ElasticLoadBalancingV2::Listener' 534 | Properties: 535 | DefaultActions: 536 | Fn::If: 537 | - CustomDomain 538 | - - Type: "redirect" 539 | RedirectConfig: 540 | Protocol: "HTTPS" 541 | Port: 443 542 | Host: "#{host}" 543 | Path: "/#{path}" 544 | Query: "#{query}" 545 | StatusCode: "HTTP_301" 546 | - - Type: forward 547 | TargetGroupArn: !Ref LoadBalancerTargetGroup 548 | LoadBalancerArn: !Ref ElasticLoadBalancer 549 | Port: '80' 550 | Protocol: HTTP 551 | 552 | HttpsLoadBalancerListener: 553 | Condition: CustomDomain 554 | Type: 'AWS::ElasticLoadBalancingV2::Listener' 555 | Properties: 556 | Certificates: 557 | - CertificateArn: !Ref AlbCertificate 558 | DefaultActions: 559 | - Type: forward 560 | TargetGroupArn: !Ref LoadBalancerTargetGroup 561 | LoadBalancerArn: !Ref ElasticLoadBalancer 562 | Port: '443' 563 | Protocol: HTTPS 564 | 565 | LoadBalancerListenerRule: 566 | Type: 'AWS::ElasticLoadBalancingV2::ListenerRule' 567 | DependsOn: LoadBalancerListener 568 | Properties: 569 | Actions: 570 | - Type: forward 571 | TargetGroupArn: !Ref LoadBalancerTargetGroup 572 | Conditions: 573 | - Field: "path-pattern" 574 | Values: 575 | - "/" 576 | ListenerArn: !Ref LoadBalancerListener 577 | Priority: 1 578 | 579 | LoadBalancerTargetGroup: 580 | Type: 'AWS::ElasticLoadBalancingV2::TargetGroup' 581 | DependsOn: ElasticLoadBalancer 582 | Properties: 583 | HealthCheckIntervalSeconds: 10 # This might be keeping the Serverless RDS awake 584 | HealthCheckPath: /status.php 585 | HealthCheckProtocol: HTTP 586 | HealthCheckTimeoutSeconds: 5 587 | HealthyThresholdCount: 2 588 | Port: 80 589 | Protocol: HTTP 590 | Matcher: 591 | HttpCode: 200,400 592 | UnhealthyThresholdCount: 2 593 | VpcId: !GetAtt VpcStack.Outputs.VPC 594 | TargetType: ip 595 | TargetGroupAttributes: 596 | - Key: deregistration_delay.timeout_seconds 597 | Value: 60 598 | - Key: stickiness.enabled 599 | Value: false 600 | 601 | EcsService: 602 | DependsOn: 603 | - LoadBalancerListener 604 | - EfsMountTarget1 605 | - EfsMountTarget2 606 | Type: 'AWS::ECS::Service' 607 | Properties: 608 | Cluster: !Ref EcsCluster 609 | DesiredCount: !Ref EcsInitialDesiredCapacity 610 | CapacityProviderStrategy: 611 | - Base: 1 612 | CapacityProvider: !Ref EcsCapacityProvider 613 | Weight: 1 614 | DeploymentConfiguration: 615 | MaximumPercent: 100 616 | MinimumHealthyPercent: 0 # Allows service interruption rather than scaling up 617 | NetworkConfiguration: 618 | AwsvpcConfiguration: 619 | AssignPublicIp: !If [PrivateSubnets, DISABLED, ENABLED] 620 | SecurityGroups: 621 | - !Ref EcsSecurityGroup 622 | Subnets: !Split [ ',', !If [PrivateSubnets, !GetAtt VpcStack.Outputs.PrivateSubnets, !GetAtt VpcStack.Outputs.PublicSubnets] ] 623 | HealthCheckGracePeriodSeconds: 2500 624 | LoadBalancers: 625 | - ContainerName: 'nextcloud' 626 | ContainerPort: '80' 627 | TargetGroupArn: !Ref LoadBalancerTargetGroup 628 | SchedulingStrategy: REPLICA 629 | TaskDefinition: !Ref EcsTaskDefinition 630 | PropagateTags: SERVICE 631 | 632 | ECSTaskRole: 633 | Type: 'AWS::IAM::Role' 634 | Properties: 635 | AssumeRolePolicyDocument: 636 | Statement: 637 | - Effect: Allow 638 | Principal: 639 | Service: 640 | - ecs-tasks.amazonaws.com 641 | Action: 642 | - 'sts:AssumeRole' 643 | Path: / 644 | ManagedPolicyArns: 645 | - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess 646 | - 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole' 647 | Policies: 648 | - PolicyName: ecs-service 649 | PolicyDocument: 650 | Statement: 651 | - Effect: Allow 652 | Action: 653 | - 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer' 654 | - 'elasticloadbalancing:DeregisterTargets' 655 | - 'elasticloadbalancing:Describe*' 656 | - 'elasticloadbalancing:RegisterInstancesWithLoadBalancer' 657 | - 'elasticloadbalancing:RegisterTargets' 658 | # TODO: understand permissions needed 659 | Resource: '*' 660 | - Effect: Allow 661 | Action: 662 | - 'ec2:Describe*' 663 | - 'ec2:AuthorizeSecurityGroupIngress' 664 | - "ec2:AttachNetworkInterface" 665 | - "ec2:CreateNetworkInterface" 666 | - "ec2:CreateNetworkInterfacePermission" 667 | - "ec2:DeleteNetworkInterface" 668 | - "ec2:DeleteNetworkInterfacePermission" 669 | - "ec2:Describe*" 670 | - "ec2:DetachNetworkInterface" 671 | Resource: "*" 672 | - Effect: Allow 673 | Action: 674 | - elasticfilesystem:* 675 | Resource: 676 | - !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:file-system/${Efs} 677 | - !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPNextcloud} 678 | - !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPConfig} 679 | - !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPApps} 680 | - !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPData} 681 | 682 | ECSTaskExecRole: 683 | Type: 'AWS::IAM::Role' 684 | Properties: 685 | AssumeRolePolicyDocument: 686 | Statement: 687 | - Effect: Allow 688 | Principal: 689 | Service: 690 | - ecs-tasks.amazonaws.com 691 | Action: 692 | - 'sts:AssumeRole' 693 | Path: / 694 | ManagedPolicyArns: 695 | - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess 696 | - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy 697 | Policies: 698 | - PolicyName: ecs-service 699 | PolicyDocument: 700 | Statement: 701 | - Effect: Allow 702 | Action: 703 | - elasticfilesystem:* 704 | Resource: 705 | - !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:file-system/${Efs} 706 | - !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPNextcloud} 707 | - !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPConfig} 708 | - !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPApps} 709 | - !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPData} 710 | - Effect: Allow 711 | Action: 712 | - "ecr:GetAuthorizationToken" 713 | - "ecr:BatchCheckLayerAvailability" 714 | - "ecr:GetDownloadUrlForLayer" 715 | - "ecr:BatchGetImage" 716 | Resource: "*" 717 | - Effect: Allow 718 | Action: 719 | - s3:* 720 | Resource: !Sub arn:aws:s3:::${DataBucket} 721 | - Effect: Allow 722 | Action: 723 | - s3:* 724 | Resource: !Sub arn:aws:s3:::${DataBucket}/* 725 | - Effect: Deny 726 | Action: 727 | - s3:DeleteBucket* 728 | - s3:PutBucket* 729 | - s3:PutEncryptionConfiguration 730 | - s3:CreateBucket 731 | Resource: "*" 732 | - Effect: Allow 733 | Action: 734 | - s3:GetBucketLocation 735 | Resource: arn:aws:s3:::* 736 | 737 | Ec2NcRole: 738 | Type: 'AWS::IAM::Role' 739 | Properties: 740 | AssumeRolePolicyDocument: 741 | Statement: 742 | - Effect: Allow 743 | Principal: 744 | Service: 745 | - ec2.amazonaws.com 746 | Action: 747 | - 'sts:AssumeRole' 748 | Path: / 749 | ManagedPolicyArns: 750 | - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore 751 | Policies: 752 | - PolicyName: ecs-service 753 | PolicyDocument: 754 | Statement: 755 | - Effect: Allow 756 | Action: 757 | - logs:* 758 | - elasticfilesystem:* 759 | Resource: '*' 760 | InstanceProfile: 761 | Type: "AWS::IAM::InstanceProfile" 762 | Properties: 763 | Path: "/" 764 | Roles: 765 | - Ref: Ec2NcRole 766 | RedisSecurityGroup: 767 | Type: 'AWS::EC2::SecurityGroup' 768 | Properties: 769 | GroupDescription: Elasticache Security Group 770 | VpcId: !GetAtt VpcStack.Outputs.VPC 771 | RedisSecurityGroupinbound: 772 | Type: 'AWS::EC2::SecurityGroupIngress' 773 | Properties: 774 | GroupId: !Ref RedisSecurityGroup 775 | IpProtocol: tcp 776 | FromPort: '6379' 777 | ToPort: '6379' 778 | SourceSecurityGroupId: !Ref EcsSecurityGroup 779 | RedisSubnetGroup: 780 | Type: 'AWS::ElastiCache::SubnetGroup' 781 | Properties: 782 | Description: Redis Subnet Group 783 | SubnetIds: !Split [ ',', !GetAtt VpcStack.Outputs.PrivateSubnets] 784 | RedisParameterGroup: 785 | Type: AWS::ElastiCache::ParameterGroup 786 | Properties: 787 | Description: nextcloud param group 788 | CacheParameterGroupFamily: redis6.x 789 | Properties: 790 | cluster-enabled: 'no' 791 | RedisReplicationGroup: 792 | DeletionPolicy: Snapshot 793 | UpdateReplacePolicy: Snapshot 794 | Type: AWS::ElastiCache::ReplicationGroup 795 | Properties: 796 | ReplicationGroupDescription: 'redis cache for nextcloud' 797 | AutomaticFailoverEnabled: false 798 | NumCacheClusters: 1 799 | MultiAZEnabled: false 800 | CacheNodeType: !Ref RedisTshirtSize 801 | CacheParameterGroupName: !Ref RedisParameterGroup 802 | CacheSubnetGroupName: !Ref RedisSubnetGroup 803 | Engine: redis 804 | EngineVersion: 6.x 805 | # NumNodeGroups: 1 806 | # ReplicasPerNodeGroup: 1 807 | # NodeGroupConfiguration: 808 | # - ReplicaCount: 1 809 | PreferredMaintenanceWindow: 'sat:07:00-sat:08:00' 810 | SecurityGroupIds: 811 | - !GetAtt 812 | - RedisSecurityGroup 813 | - GroupId 814 | SnapshotRetentionLimit: 35 815 | SnapshotWindow: '00:00-03:00' 816 | AtRestEncryptionEnabled: true 817 | UpdatePolicy: 818 | UseOnlineResharding: true 819 | NextcloudAutoScalingRole: 820 | Type: AWS::IAM::Role 821 | Properties: 822 | AssumeRolePolicyDocument: 823 | Statement: 824 | - Effect: Allow 825 | Principal: 826 | Service: ecs-tasks.amazonaws.com 827 | Action: 'sts:AssumeRole' 828 | ManagedPolicyArns: 829 | - 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole' 830 | NextcloudAutoScalingTarget: 831 | Type: AWS::ApplicationAutoScaling::ScalableTarget 832 | Properties: 833 | MinCapacity: !Ref EcsMinCapacity 834 | MaxCapacity: !Ref EcsMaxCapacity 835 | ResourceId: !Join [ '/', [ service, !Ref EcsCluster, !GetAtt EcsService.Name ] ] 836 | ScalableDimension: ecs:service:DesiredCount 837 | ServiceNamespace: ecs 838 | RoleARN: !GetAtt NextcloudAutoScalingRole.Arn 839 | SuspendedState: 840 | DynamicScalingInSuspended: !Ref SuspendAutoScaling 841 | DynamicScalingOutSuspended: !Ref SuspendAutoScaling 842 | ScheduledScalingSuspended: !Ref SuspendAutoScaling 843 | NextcloudAutoScalingPolicy: 844 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 845 | Properties: 846 | PolicyName: !GetAtt EcsService.Name 847 | PolicyType: TargetTrackingScaling 848 | ScalingTargetId: !Ref NextcloudAutoScalingTarget 849 | TargetTrackingScalingPolicyConfiguration: 850 | PredefinedMetricSpecification: 851 | PredefinedMetricType: ECSServiceAverageCPUUtilization 852 | ScaleInCooldown: 10 853 | ScaleOutCooldown: 10 854 | # Keep things at or lower than a target CPU utilization, for example 855 | TargetValue: !Ref EcsTargetCpuUtilization 856 | NextcloudCWDashbard: 857 | Type: AWS::CloudWatch::Dashboard 858 | Properties: 859 | DashboardName: !Sub ${DeploymentName}-nextcloud 860 | DashboardBody: !Sub | 861 | { 862 | "widgets": [ 863 | { 864 | "height": 6, 865 | "width": 12, 866 | "y": 0, 867 | "x": 0, 868 | "type": "metric", 869 | "properties": { 870 | "view": "timeSeries", 871 | "stacked": false, 872 | "metrics": [ 873 | [ "AWS/ECS", "CPUUtilization", "ServiceName", "${EcsService.Name}", "ClusterName", "${EcsCluster}" ] 874 | ], 875 | "region": "${AWS::Region}", 876 | "title": "ECS-CPUUtilization" 877 | } 878 | }, 879 | { 880 | "height": 6, 881 | "width": 12, 882 | "y": 0, 883 | "x": 12, 884 | "type": "metric", 885 | "properties": { 886 | "view": "timeSeries", 887 | "stacked": false, 888 | "region": "${AWS::Region}", 889 | "stat": "Average", 890 | "period": 300, 891 | "metrics": [ 892 | [ "AWS/ECS", "MemoryUtilization", "ServiceName", "${EcsService.Name}", "ClusterName", "${EcsCluster}" ] 893 | ], 894 | "title": "ECS-MemoryUtilization" 895 | } 896 | }, 897 | { 898 | "height": 6, 899 | "width": 12, 900 | "y": 6, 901 | "x": 0, 902 | "type": "metric", 903 | "properties": { 904 | "view": "timeSeries", 905 | "stacked": false, 906 | "region": "${AWS::Region}", 907 | "stat": "Average", 908 | "period": 300, 909 | "metrics": [ 910 | [ "AWS/RDS", "CPUUtilization", "DBClusterIdentifier", "${RdsStack.Outputs.DBIdentifier}" ] 911 | ], 912 | "title": "RDS-CPUUtilization" 913 | } 914 | }, 915 | { 916 | "height": 6, 917 | "width": 12, 918 | "y": 6, 919 | "x": 12, 920 | "type": "metric", 921 | "properties": { 922 | "view": "timeSeries", 923 | "stacked": false, 924 | "region": "${AWS::Region}", 925 | "stat": "Average", 926 | "period": 300, 927 | "metrics": [ 928 | [ "AWS/RDS", "DatabaseConnections", "DBClusterIdentifier", "${RdsStack.Outputs.DBIdentifier}" ] 929 | ], 930 | "title": "DatabaseConnections" 931 | } 932 | }, 933 | { 934 | "height": 6, 935 | "width": 12, 936 | "y": 12, 937 | "x": 0, 938 | "type": "metric", 939 | "properties": { 940 | "metrics": [ 941 | [ "AWS/EFS", "StorageBytes", "StorageClass", "Standard", "FileSystemId", "${Efs}" ], 942 | [ "...", "IA", ".", "." ], 943 | [ "...", "Total", ".", "." ] 944 | ], 945 | "view": "timeSeries", 946 | "stacked": false, 947 | "region": "${AWS::Region}", 948 | "stat": "Average", 949 | "period": 300, 950 | "title": "EFS-StorageBytes" 951 | } 952 | }, 953 | { 954 | "height": 6, 955 | "width": 12, 956 | "y": 12, 957 | "x": 12, 958 | "type": "metric", 959 | "properties": { 960 | "metrics": [ 961 | [ { "expression": "(m2*100)/m1", "label": "Data write", "id": "e2", "region": "${AWS::Region}" } ], 962 | [ { "expression": "(m3*100)/m1", "label": "Data read", "id": "e3", "region": "${AWS::Region}" } ], 963 | [ { "expression": "(m4*100)/m1", "label": "Metadata", "id": "e4", "region": "${AWS::Region}" } ], 964 | [ "AWS/EFS", "TotalIOBytes", "FileSystemId", "${Efs}", { "id": "m1", "visible": false, "region": "${AWS::Region}" } ], 965 | [ "AWS/EFS", "DataWriteIOBytes", "FileSystemId", "${Efs}", { "id": "m2", "visible": false, "region": "${AWS::Region}" } ], 966 | [ "AWS/EFS", "DataReadIOBytes", "FileSystemId", "${Efs}", { "id": "m3", "visible": false, "region": "${AWS::Region}" } ], 967 | [ "AWS/EFS", "MetadataIOBytes", "FileSystemId", "${Efs}", { "id": "m4", "visible": false, "region": "${AWS::Region}" } ] 968 | ], 969 | "view": "timeSeries", 970 | "stacked": false, 971 | "region": "${AWS::Region}", 972 | "stat": "Sum", 973 | "period": 60, 974 | "title": "EFS - Throughput by type" 975 | } 976 | }, 977 | { 978 | "height": 6, 979 | "width": 12, 980 | "y": 18, 981 | "x": 0, 982 | "type": "metric", 983 | "properties": { 984 | "view": "timeSeries", 985 | "stacked": false, 986 | "start": "-P14D", 987 | "end": "P0D", 988 | "region": "${AWS::Region}", 989 | "metrics": [ 990 | [ "AWS/S3", "BucketSizeBytes", "StorageType", "StandardStorage", "BucketName", "${DataBucket}" ] 991 | ], 992 | "period": 86400, 993 | "stat": "Average", 994 | "title": "Data-BucketSizeBytes" 995 | } 996 | }, 997 | { 998 | "height": 6, 999 | "width": 12, 1000 | "y": 18, 1001 | "x": 12, 1002 | "type": "metric", 1003 | "properties": { 1004 | "view": "timeSeries", 1005 | "stacked": false, 1006 | "start": "-P14D", 1007 | "end": "P0D", 1008 | "region": "${AWS::Region}", 1009 | "metrics": [ 1010 | [ "AWS/S3", "NumberOfObjects", "StorageType", "AllStorageTypes", "BucketName", "${DataBucket}", { "period": 86400 } ] 1011 | ], 1012 | "period": 86400, 1013 | "stat": "Average", 1014 | "title": "DataBucket-NumberOfObjects" 1015 | } 1016 | }, 1017 | { 1018 | "type": "metric", 1019 | "x": 0, 1020 | "y": 24, 1021 | "width": 12, 1022 | "height": 6, 1023 | "properties": { 1024 | "view": "timeSeries", 1025 | "stacked": false, 1026 | "metrics": [ 1027 | [ "AWS/ElastiCache", "CPUUtilization", "CacheClusterId", "${RedisReplicationGroup}-001", "CacheNodeId", "0001" ] 1028 | ], 1029 | "region": "${AWS::Region}", 1030 | "title": "Redis_CPUUtilization" 1031 | } 1032 | }, 1033 | { 1034 | "type": "metric", 1035 | "x": 12, 1036 | "y": 24, 1037 | "width": 12, 1038 | "height": 6, 1039 | "properties": { 1040 | "view": "timeSeries", 1041 | "stacked": false, 1042 | "metrics": [ 1043 | [ "AWS/ElastiCache", "DatabaseMemoryUsagePercentage", "CacheClusterId", "${RedisReplicationGroup}-001", "CacheNodeId", "0001" ] 1044 | ], 1045 | "region": "${AWS::Region}", 1046 | "title": "Redis_DatabaseMemoryUsagePercentage" 1047 | } 1048 | } 1049 | ] 1050 | } 1051 | 1052 | 1053 | Outputs: 1054 | CloudWatchDashboardUrl: 1055 | Value: !Sub https://${AWS::Region}.console.aws.amazon.com/cloudwatch/home?region=${AWS::Region}#dashboards:name=${NextcloudCWDashbard} 1056 | LoadBalancerUrl: 1057 | Value: !GetAtt ElasticLoadBalancer.DNSName 1058 | CustomUrl: 1059 | Condition: CustomDomain 1060 | Value: !Sub https://${Domain} 1061 | -------------------------------------------------------------------------------- /simplevpc.yaml: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | Description: Simple VPC Deployment 18 | 19 | Parameters: 20 | EnvironmentName: 21 | Description: An environment name that is prefixed to resource names 22 | Type: String 23 | Default: nextcloud 24 | 25 | VpcCIDR: 26 | Description: Please enter the IP range (CIDR notation) for this VPC 27 | Type: String 28 | Default: 10.192.0.0/16 29 | 30 | PublicSubnet1CIDR: 31 | Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone 32 | Type: String 33 | Default: 10.192.10.0/24 34 | 35 | PublicSubnet2CIDR: 36 | Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone 37 | Type: String 38 | Default: 10.192.11.0/24 39 | 40 | PrivateSubnet1CIDR: 41 | Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone 42 | Type: String 43 | Default: 10.192.20.0/24 44 | 45 | PrivateSubnet2CIDR: 46 | Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone 47 | Type: String 48 | Default: 10.192.21.0/24 49 | 50 | IsolationLevel: 51 | Type: String 52 | Default: Private 53 | AllowedValues: 54 | - PrivateHA 55 | - Private 56 | - Public 57 | 58 | Conditions: 59 | PrivateSubnets: !Not [!Equals [!Ref IsolationLevel, Public]] 60 | HASetup: !Equals [!Ref IsolationLevel, PrivateHA] 61 | 62 | Resources: 63 | VPC: 64 | Type: AWS::EC2::VPC 65 | Properties: 66 | CidrBlock: !Ref VpcCIDR 67 | EnableDnsSupport: true 68 | EnableDnsHostnames: true 69 | Tags: 70 | - Key: Name 71 | Value: !Ref EnvironmentName 72 | 73 | InternetGateway: 74 | Type: AWS::EC2::InternetGateway 75 | Properties: 76 | Tags: 77 | - Key: Name 78 | Value: !Ref EnvironmentName 79 | 80 | InternetGatewayAttachment: 81 | Type: AWS::EC2::VPCGatewayAttachment 82 | Properties: 83 | InternetGatewayId: !Ref InternetGateway 84 | VpcId: !Ref VPC 85 | 86 | S3GatewayEndpoint: 87 | Condition: PrivateSubnets 88 | Type: AWS::EC2::VPCEndpoint 89 | Properties: 90 | VpcId: !Ref VPC 91 | ServiceName: !Sub com.amazonaws.${AWS::Region}.s3 92 | VpcEndpointType: Gateway 93 | RouteTableIds: 94 | - !Ref PrivateRouteTable1 95 | - !Ref PrivateRouteTable2 96 | - !Ref PublicRouteTable 97 | 98 | PublicSubnet1: 99 | Type: AWS::EC2::Subnet 100 | Properties: 101 | VpcId: !Ref VPC 102 | AvailabilityZone: !Select [ 0, !GetAZs '' ] 103 | CidrBlock: !Ref PublicSubnet1CIDR 104 | MapPublicIpOnLaunch: true 105 | Tags: 106 | - Key: Name 107 | Value: !Sub ${EnvironmentName} Public Subnet (AZ1) 108 | 109 | PublicSubnet2: 110 | Type: AWS::EC2::Subnet 111 | Properties: 112 | VpcId: !Ref VPC 113 | AvailabilityZone: !Select [ 1, !GetAZs '' ] 114 | CidrBlock: !Ref PublicSubnet2CIDR 115 | MapPublicIpOnLaunch: true 116 | Tags: 117 | - Key: Name 118 | Value: !Sub ${EnvironmentName} Public Subnet (AZ2) 119 | 120 | PrivateSubnet1: 121 | Type: AWS::EC2::Subnet 122 | Properties: 123 | VpcId: !Ref VPC 124 | AvailabilityZone: !Select [ 0, !GetAZs '' ] 125 | CidrBlock: !Ref PrivateSubnet1CIDR 126 | MapPublicIpOnLaunch: false 127 | Tags: 128 | - Key: Name 129 | Value: !Sub ${EnvironmentName} Private Subnet (AZ1) 130 | 131 | PrivateSubnet2: 132 | Type: AWS::EC2::Subnet 133 | Properties: 134 | VpcId: !Ref VPC 135 | AvailabilityZone: !Select [ 1, !GetAZs '' ] 136 | CidrBlock: !Ref PrivateSubnet2CIDR 137 | MapPublicIpOnLaunch: false 138 | Tags: 139 | - Key: Name 140 | Value: !Sub ${EnvironmentName} Private Subnet (AZ2) 141 | 142 | NatGateway1EIP: 143 | Condition: PrivateSubnets 144 | Type: AWS::EC2::EIP 145 | DependsOn: InternetGatewayAttachment 146 | Properties: 147 | Domain: vpc 148 | 149 | NatGateway2EIP: 150 | Condition: HASetup 151 | Type: AWS::EC2::EIP 152 | DependsOn: InternetGatewayAttachment 153 | Properties: 154 | Domain: vpc 155 | 156 | NatGateway1: 157 | Condition: PrivateSubnets 158 | Type: AWS::EC2::NatGateway 159 | Properties: 160 | AllocationId: !GetAtt NatGateway1EIP.AllocationId 161 | SubnetId: !Ref PublicSubnet1 162 | 163 | NatGateway2: 164 | Condition: HASetup 165 | Type: AWS::EC2::NatGateway 166 | Properties: 167 | AllocationId: !GetAtt NatGateway2EIP.AllocationId 168 | SubnetId: !Ref PublicSubnet2 169 | 170 | PublicRouteTable: 171 | Type: AWS::EC2::RouteTable 172 | Properties: 173 | VpcId: !Ref VPC 174 | Tags: 175 | - Key: Name 176 | Value: !Sub ${EnvironmentName} Public Routes 177 | 178 | DefaultPublicRoute: 179 | Type: AWS::EC2::Route 180 | DependsOn: InternetGatewayAttachment 181 | Properties: 182 | RouteTableId: !Ref PublicRouteTable 183 | DestinationCidrBlock: 0.0.0.0/0 184 | GatewayId: !Ref InternetGateway 185 | 186 | PublicSubnet1RouteTableAssociation: 187 | Type: AWS::EC2::SubnetRouteTableAssociation 188 | Properties: 189 | RouteTableId: !Ref PublicRouteTable 190 | SubnetId: !Ref PublicSubnet1 191 | 192 | PublicSubnet2RouteTableAssociation: 193 | Type: AWS::EC2::SubnetRouteTableAssociation 194 | Properties: 195 | RouteTableId: !Ref PublicRouteTable 196 | SubnetId: !Ref PublicSubnet2 197 | 198 | 199 | PrivateRouteTable1: 200 | Type: AWS::EC2::RouteTable 201 | Properties: 202 | VpcId: !Ref VPC 203 | Tags: 204 | - Key: Name 205 | Value: !Sub ${EnvironmentName} Private Routes (AZ1) 206 | 207 | DefaultPrivateRoute1: 208 | Condition: PrivateSubnets 209 | Type: AWS::EC2::Route 210 | Properties: 211 | RouteTableId: !Ref PrivateRouteTable1 212 | DestinationCidrBlock: 0.0.0.0/0 213 | NatGatewayId: !Ref NatGateway1 214 | 215 | PrivateSubnet1RouteTableAssociation: 216 | Type: AWS::EC2::SubnetRouteTableAssociation 217 | Properties: 218 | RouteTableId: !Ref PrivateRouteTable1 219 | SubnetId: !Ref PrivateSubnet1 220 | 221 | PrivateRouteTable2: 222 | Type: AWS::EC2::RouteTable 223 | Properties: 224 | VpcId: !Ref VPC 225 | Tags: 226 | - Key: Name 227 | Value: !Sub ${EnvironmentName} Private Routes (AZ2) 228 | 229 | DefaultPrivateRoute2: 230 | Condition: PrivateSubnets 231 | Type: AWS::EC2::Route 232 | Properties: 233 | RouteTableId: !Ref PrivateRouteTable2 234 | DestinationCidrBlock: 0.0.0.0/0 235 | NatGatewayId: !If [HASetup, !Ref NatGateway2, !Ref NatGateway1] 236 | 237 | PrivateSubnet2RouteTableAssociation: 238 | Type: AWS::EC2::SubnetRouteTableAssociation 239 | Properties: 240 | RouteTableId: !Ref PrivateRouteTable2 241 | SubnetId: !Ref PrivateSubnet2 242 | 243 | Outputs: 244 | VPC: 245 | Description: A reference to the created VPC 246 | Value: !Ref VPC 247 | 248 | PublicSubnets: 249 | Description: A list of the public subnets 250 | Value: !Join [ ",", [ !Ref PublicSubnet1, !Ref PublicSubnet2 ] ] 251 | 252 | PrivateSubnets: 253 | Description: A list of the private subnets 254 | Value: !Join [ ",", [ !Ref PrivateSubnet1, !Ref PrivateSubnet2 ] ] 255 | 256 | PublicSubnet1: 257 | Description: A reference to the public subnet in the 1st Availability Zone 258 | Value: !Ref PublicSubnet1 259 | 260 | PublicSubnet2: 261 | Description: A reference to the public subnet in the 2nd Availability Zone 262 | Value: !Ref PublicSubnet2 263 | 264 | PrivateSubnet1: 265 | Description: A reference to the private subnet in the 1st Availability Zone 266 | Value: !Ref PrivateSubnet1 267 | 268 | PrivateSubnet2: 269 | Description: A reference to the private subnet in the 2nd Availability Zone 270 | Value: !Ref PrivateSubnet2 271 | --------------------------------------------------------------------------------