Esempio n. 1
0
    def __init__(self, scope: Construct, id: str, *, deployment: Deployment,
                 policy: Policy, cluster: ICluster, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        Tags.of(self).add("Application", self.application_name)
        Tags.of(self).add("Deployment", deployment.value)

        policy.add_stack(self)

        if deployment == Deployment.PRODUCTION:
            desired_count = 1  # Currently this pod is stateful, and as such cannot be run more than once
            priority = 46
        else:
            desired_count = 1
            priority = 146

        api_fqdn = dns.subdomain_to_fqdn("api.bananas")
        api_url = f"https://{api_fqdn}"
        frontend_fqdn = dns.subdomain_to_fqdn(self.subdomain_name)
        frontend_url = f"https://{frontend_fqdn}"

        sentry_dsn = parameter_store.add_secure_string(
            f"/BananasFrontendWeb/{deployment.value}/SentryDSN").parameter

        ECSHTTPSContainer(
            self,
            self.application_name,
            subdomain_name=self.subdomain_name,
            deployment=deployment,
            policy=policy,
            application_name=self.application_name,
            image_name="ghcr.io/openttd/bananas-frontend-web",
            port=80,
            memory_limit_mib=64,
            desired_count=desired_count,
            cluster=cluster,
            priority=priority,
            command=[
                "--api-url",
                api_url,
                "--frontend-url",
                frontend_url,
                "run",
                "-p",
                "80",
                "-h",
                "0.0.0.0",
            ],
            environment={
                "WEBCLIENT_SENTRY_ENVIRONMENT": deployment.value.lower(),
            },
            secrets={
                "WEBCLIENT_SENTRY_DSN": Secret.from_ssm_parameter(sentry_dsn),
            },
        )
Esempio n. 2
0
    def __init__(self, scope: Construct, id: str, *, deployment: Deployment,
                 policy: Policy, cluster: ICluster, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        Tags.of(self).add("Application", self.application_name)
        Tags.of(self).add("Deployment", deployment.value)

        policy.add_stack(self)

        if deployment == Deployment.PRODUCTION:
            desired_count = 2
            priority = 62
        else:
            desired_count = 1
            priority = 162

        api_fqdn = dns.subdomain_to_fqdn("api.master")
        api_url = f"https://{api_fqdn}"

        sentry_dsn = parameter_store.add_secure_string(
            f"/MasterServerWeb/{deployment.value}/SentryDSN").parameter

        ECSHTTPSContainer(
            self,
            self.application_name,
            subdomain_name=self.subdomain_name,
            deployment=deployment,
            policy=policy,
            application_name=self.application_name,
            image_name="ghcr.io/openttd/master-server-web",
            port=80,
            memory_limit_mib=96,
            desired_count=desired_count,
            cluster=cluster,
            priority=priority,
            command=[
                "--api-url",
                api_url,
                "run",
                "-p",
                "80",
                "-h",
                "0.0.0.0",
            ],
            environment={
                "WEBCLIENT_SENTRY_ENVIRONMENT": deployment.value.lower(),
            },
            secrets={
                "WEBCLIENT_SENTRY_DSN": Secret.from_ssm_parameter(sentry_dsn),
            },
        )
Esempio n. 3
0
    def __init__(
        self,
        scope: Construct,
        id: str,
        *,
        deployment: Deployment,
        policy: Policy,
        cluster: ICluster,
        bucket: Bucket,
        **kwargs,
    ) -> None:
        super().__init__(scope, id, **kwargs)

        Tags.of(self).add("Application", self.application_name)
        Tags.of(self).add("Deployment", deployment.value)

        policy.add_stack(self)

        if deployment == Deployment.PRODUCTION:
            desired_count = 2
            priority = 44
            memory = 256
            github_url = "https://github.com/OpenTTD/BaNaNaS"
            content_port = 3978
            bootstrap_command = ["--bootstrap-unique-id", "4f474658"]
        else:
            desired_count = 1
            priority = 144
            memory = 128
            github_url = "https://github.com/OpenTTD/BaNaNaS-staging"
            content_port = 4978
            bootstrap_command = []

        cdn_fqdn = dns.subdomain_to_fqdn("bananas.cdn")
        cdn_url = f"http://{cdn_fqdn}"

        sentry_dsn = parameter_store.add_secure_string(f"/BananasServer/{deployment.value}/SentryDSN").parameter
        reload_secret = parameter_store.add_secure_string(f"/BananasServer/{deployment.value}/ReloadSecret").parameter

        command = [
            "--storage",
            "s3",
            "--storage-s3-bucket",
            bucket.bucket_name,
            "--index",
            "github",
            "--index-github-url",
            github_url,
            "--cdn-url",
            cdn_url,
            "--bind",
            "0.0.0.0",
            "--content-port",
            str(content_port),
            "--proxy-protocol",
        ]
        command.extend(bootstrap_command)

        self.container = ECSHTTPSContainer(
            self,
            self.application_name,
            subdomain_name=self.subdomain_name,
            path_pattern=self.path_pattern,
            allow_via_http=True,
            deployment=deployment,
            policy=policy,
            application_name=self.application_name,
            image_name="ghcr.io/openttd/bananas-server",
            port=80,
            memory_limit_mib=memory,
            desired_count=desired_count,
            cluster=cluster,
            priority=priority,
            command=command,
            environment={
                "BANANAS_SERVER_SENTRY_ENVIRONMENT": deployment.value.lower(),
            },
            secrets={
                "BANANAS_SERVER_SENTRY_DSN": Secret.from_ssm_parameter(sentry_dsn),
                "BANANAS_SERVER_RELOAD_SECRET": Secret.from_ssm_parameter(reload_secret),
            },
        )

        self.container.add_port(content_port)
        nlb.add_nlb(self, self.container.service, Port.tcp(content_port), self.nlb_subdomain_name, "BaNaNaS Server")

        self.container.task_role.add_to_policy(
            PolicyStatement(
                actions=[
                    "s3:GetObject",
                    "s3:ListBucket",
                ],
                resources=[
                    bucket.bucket_arn,
                    StringConcat().join(bucket.bucket_arn, "/*"),
                ],
            )
        )
Esempio n. 4
0
    def __init__(
        self,
        scope: Construct,
        id: str,
        *,
        deployment: Deployment,
        policy: Policy,
        cluster: ICluster,
        bucket: Bucket,
        **kwargs,
    ) -> None:
        super().__init__(scope, id, **kwargs)

        Tags.of(self).add("Application", self.application_name)
        Tags.of(self).add("Deployment", deployment.value)

        policy.add_stack(self)

        if deployment == Deployment.PRODUCTION:
            desired_count = 1  # Currently this pod is stateful, and as such cannot be run more than once
            tus_priority = 40
            priority = 42
            memory = 256
            github_url = "[email protected]:OpenTTD/BaNaNaS.git"
            client_file = "clients-production.yaml"
        else:
            desired_count = 1
            tus_priority = 140
            priority = 142
            memory = 96
            github_url = "[email protected]:OpenTTD/BaNaNaS-staging.git"
            client_file = "clients-staging.yaml"

        sentry_dsn = parameter_store.add_secure_string(f"/BananasApi/{deployment.value}/SentryDSN").parameter
        user_github_client_id = parameter_store.add_secure_string(f"/BananasApi/{deployment.value}/UserGithubClientId").parameter
        user_github_client_secret = parameter_store.add_secure_string(f"/BananasApi/{deployment.value}/UserGithubClientSecret").parameter
        index_github_private_key = parameter_store.add_secure_string(f"/BananasApi/{deployment.value}/IndexGithubPrivateKey").parameter
        reload_secret = parameter_store.add_secure_string(f"/BananasApi/{deployment.value}/ReloadSecret").parameter

        self.container = ECSHTTPSContainer(
            self,
            self.application_name,
            subdomain_name=self.subdomain_name,
            deployment=deployment,
            policy=policy,
            application_name=self.application_name,
            image_name="ghcr.io/openttd/bananas-api",
            port=80,
            memory_limit_mib=memory,
            desired_count=desired_count,
            cluster=cluster,
            priority=priority,
            command=[
                "--storage",
                "s3",
                "--storage-s3-bucket",
                bucket.bucket_name,
                "--index",
                "github",
                "--index-github-url",
                github_url,
                "--client-file",
                client_file,
                "--user",
                "github",
                "--bind",
                "0.0.0.0",
                "--behind-proxy",
            ],
            environment={
                "BANANAS_API_SENTRY_ENVIRONMENT": deployment.value.lower(),
            },
            secrets={
                "BANANAS_API_SENTRY_DSN": Secret.from_ssm_parameter(sentry_dsn),
                "BANANAS_API_USER_GITHUB_CLIENT_ID": Secret.from_ssm_parameter(user_github_client_id),
                "BANANAS_API_USER_GITHUB_CLIENT_SECRET": Secret.from_ssm_parameter(user_github_client_secret),
                "BANANAS_API_INDEX_GITHUB_PRIVATE_KEY": Secret.from_ssm_parameter(index_github_private_key),
                "BANANAS_API_RELOAD_SECRET": Secret.from_ssm_parameter(reload_secret),
            },
        )
        self.container.add_port(1080)
        self.container.add_target(
            subdomain_name=self.subdomain_name,
            port=1080,
            priority=tus_priority,
            path_pattern="/new-package/tus/*",
        )

        self.container.task_role.add_to_policy(
            PolicyStatement(
                actions=[
                    "s3:PutObject",
                    "s3:PutObjectAcl",
                ],
                resources=[
                    StringConcat().join(bucket.bucket_arn, "/*"),
                ],
            )
        )
Esempio n. 5
0
    def __init__(
        self,
        scope: Construct,
        id: str,
        *,
        deployment: Deployment,
        policy: Policy,
        cluster: ICluster,
        vpc: IVpc,
        **kwargs,
    ) -> None:
        super().__init__(scope, id, **kwargs)

        Tags.of(self).add("Application", self.application_name)
        Tags.of(self).add("Deployment", deployment.value)

        policy.add_stack(self)

        efs_cache = FileSystem(
            self,
            "WikiCacheEFS",
            vpc=vpc,
        )
        efs_cache.connections.allow_default_port_from(cluster)

        if deployment == Deployment.PRODUCTION:
            desired_count = 1  # Currently this pod is stateful, and as such cannot be run more than once
            priority = 80
            memory = 384
            github_url = "[email protected]:OpenTTD/wiki-data.git"
            github_history_url = "https://github.com/OpenTTD/wiki-data"
            frontend_url = "https://wiki.openttd.org"
        else:
            desired_count = 1
            priority = 180
            memory = 128
            github_url = "[email protected]:OpenTTD/wiki-data-staging.git"
            github_history_url = "https://github.com/OpenTTD/wiki-data-staging"
            frontend_url = "https://wiki.staging.openttd.org"

        sentry_dsn = parameter_store.add_secure_string(f"/Wiki/{deployment.value}/SentryDSN").parameter
        user_github_client_id = parameter_store.add_secure_string(f"/Wiki/{deployment.value}/UserGithubClientId").parameter
        user_github_client_secret = parameter_store.add_secure_string(f"/Wiki/{deployment.value}/UserGithubClientSecret").parameter
        storage_github_private_key = parameter_store.add_secure_string(f"/Wiki/{deployment.value}/StorageGithubPrivateKey").parameter
        reload_secret = parameter_store.add_secure_string(f"/Wiki/{deployment.value}/ReloadSecret").parameter

        self.container = ECSHTTPSContainer(
            self,
            self.application_name,
            subdomain_name=self.subdomain_name,
            deployment=deployment,
            policy=policy,
            application_name=self.application_name,
            image_name="ghcr.io/truebrain/truewiki",
            port=80,
            memory_limit_mib=memory,
            desired_count=desired_count,
            cluster=cluster,
            priority=priority,
            command=[
                "--storage",
                "github",
                "--storage-github-url",
                github_url,
                "--storage-github-history-url",
                github_history_url,
                "--storage-folder",
                "/data",
                "--user",
                "github",
                "--frontend-url",
                frontend_url,
                "--cache-metadata-file",
                "/cache/metadata.json",
                "--cache-page-folder",
                "/cache-pages",
                "--bind",
                "0.0.0.0",
            ],
            environment={
                "TRUEWIKI_SENTRY_ENVIRONMENT": deployment.value.lower(),
            },
            secrets={
                "TRUEWIKI_SENTRY_DSN": Secret.from_ssm_parameter(sentry_dsn),
                "TRUEWIKI_USER_GITHUB_CLIENT_ID": Secret.from_ssm_parameter(user_github_client_id),
                "TRUEWIKI_USER_GITHUB_CLIENT_SECRET": Secret.from_ssm_parameter(user_github_client_secret),
                "TRUEWIKI_STORAGE_GITHUB_PRIVATE_KEY": Secret.from_ssm_parameter(storage_github_private_key),
                "TRUEWIKI_RELOAD_SECRET": Secret.from_ssm_parameter(reload_secret),
            },
            volumes={
                "/cache": Volume(
                    name="cache",
                    efs_volume_configuration=EfsVolumeConfiguration(
                        file_system_id=efs_cache.file_system_id,
                    ),
                ),
            },
        )
Esempio n. 6
0
    def __init__(
        self,
        scope: Construct,
        id: str,
        *,
        deployment: Deployment,
        policy: Policy,
        cluster: ICluster,
        vpc: IVpc,
        **kwargs,
    ) -> None:
        super().__init__(scope, id, **kwargs)

        Tags.of(self).add("Application", self.application_name)
        Tags.of(self).add("Deployment", deployment.value)

        policy.add_stack(self)

        efs = FileSystem(
            self,
            "EintsEFS",
            vpc=vpc,
        )
        efs.connections.allow_default_port_from(cluster)

        if deployment == Deployment.PRODUCTION:
            desired_count = 1  # Currently this pod is stateful, and as such cannot be run more than once
            priority = 70
            memory = 512
        else:
            desired_count = 1
            priority = 170
            memory = 128

        github_org_api_token = parameter_store.add_secure_string(
            f"/Eints/{deployment.value}/GithubOrgApiToken").parameter
        github_oauth2_client_id = parameter_store.add_secure_string(
            f"/Eints/{deployment.value}/GithubOauth2ClientId").parameter
        github_oauth2_client_secret = parameter_store.add_secure_string(
            f"/Eints/{deployment.value}/GithubOauth2ClientSecret").parameter
        translators_password = parameter_store.add_secure_string(
            f"/Eints/{deployment.value}/TranslatorsPassword").parameter
        sentry_dsn = parameter_store.add_secure_string(
            f"/Eints/{deployment.value}/SentryDSN").parameter

        ECSHTTPSContainer(
            self,
            self.application_name,
            subdomain_name=self.subdomain_name,
            deployment=deployment,
            policy=policy,
            application_name=self.application_name,
            image_name="ghcr.io/openttd/eints-openttd-github",
            port=80,
            memory_limit_mib=memory,
            desired_count=desired_count,
            cluster=cluster,
            priority=priority,
            command=[
                "--server-host",
                "0.0.0.0",
                "--server-port",
                "80",
                "--server-mode",
                "production",
                "--authentication",
                "github",
                "--stable-languages",
                "stable_languages",
                "--unstable-languages",
                "unstable_languages",
                "--project-cache",
                "1",
                "--project-types",
                "openttd",
                "--storage-format",
                "split-languages",
                "--data-format",
                "json",
                "--language-file-size",
                "10000000",
                "--num-backup-files",
                "1",
                "--max-num-changes",
                "5",
                "--min-num-changes",
                "2",
                "--change-stable-age",
                "600",
                "--github-organization",
                "OpenTTD",
            ],
            environment={
                "EINTS_SENTRY_ENVIRONMENT": deployment.value.lower(),
            },
            secrets={
                "EINTS_GITHUB_ORG_API_TOKEN":
                Secret.from_ssm_parameter(github_org_api_token),
                "EINTS_GITHUB_OAUTH2_CLIENT_ID":
                Secret.from_ssm_parameter(github_oauth2_client_id),
                "EINTS_GITHUB_OAUTH2_CLIENT_SECRET":
                Secret.from_ssm_parameter(github_oauth2_client_secret),
                "EINTS_TRANSLATORS_PASSWORD":
                Secret.from_ssm_parameter(translators_password),
                "EINTS_SENTRY_DSN":
                Secret.from_ssm_parameter(sentry_dsn),
            },
            volumes={
                "/data":
                Volume(
                    name="data",
                    efs_volume_configuration=EfsVolumeConfiguration(
                        file_system_id=efs.file_system_id, ),
                )
            },
        )
Esempio n. 7
0
    def __init__(
        self,
        scope: Construct,
        id: str,
        *,
        deployment: Deployment,
        policy: Policy,
        cluster: ICluster,
        vpc: IVpc,
        **kwargs,
    ) -> None:
        super().__init__(scope, id, **kwargs)

        Tags.of(self).add("Application", self.application_name)
        Tags.of(self).add("Deployment", deployment.value)

        policy.add_stack(self)

        efs_seen = FileSystem(
            self,
            "DorpsGekSeenEFS",
            vpc=vpc,
        )
        efs_seen.connections.allow_default_port_from(cluster)
        efs_logs = FileSystem(
            self,
            "DorpsGekLogsEFS",
            vpc=vpc,
        )
        efs_logs.connections.allow_default_port_from(cluster)

        if deployment == Deployment.PRODUCTION:
            desired_count = 1
            priority = 30
            addressed_by = "@"
            irc_username = "******"
            channels = [
                "--channel",
                "dorpsgek",
                "--channel",
                "openttd,public",
                "--channel",
                "openttd.dev,public",
                "--channel",
                "openttd.notice",
                "--channel",
                "openttd.tgp",
                "--channel",
                "opendune,public",
            ]
        else:
            desired_count = 1
            priority = 130
            addressed_by = "%"
            irc_username = "******"
            channels = [
                "--channel",
                "dorpsgek",
                "--channel",
                "dorpsgek-test,public",
            ]

        sentry_dsn = parameter_store.add_secure_string(
            f"/Dorpsgek/{deployment.value}/SentryDSN").parameter
        github_app_id = parameter_store.add_secure_string(
            f"/Dorpsgek/{deployment.value}/GithubAppId").parameter
        github_app_private_key = parameter_store.add_secure_string(
            f"/Dorpsgek/{deployment.value}/GithubAppPrivateKey").parameter
        github_app_secret = parameter_store.add_secure_string(
            f"/Dorpsgek/{deployment.value}/GithubAppSecret").parameter
        nickserv_password = parameter_store.add_secure_string(
            f"/Dorpsgek/{deployment.value}/NickservPassword").parameter

        ECSHTTPSContainer(
            self,
            self.application_name,
            subdomain_name=self.subdomain_name,
            deployment=deployment,
            policy=policy,
            application_name=self.application_name,
            image_name="ghcr.io/openttd/dorpsgek",
            port=80,
            memory_limit_mib=96,
            desired_count=desired_count,
            cluster=cluster,
            priority=priority,
            command=[
                "--irc-username",
                irc_username,
                "--nickserv-username",
                irc_username,
                "--addressed-by",
                addressed_by,
            ] + channels,
            environment={
                "DORPSGEK_SENTRY_ENVIRONMENT": deployment.value.lower(),
            },
            secrets={
                "DORPSGEK_SENTRY_DSN":
                Secret.from_ssm_parameter(sentry_dsn),
                "DORPSGEK_GITHUB_APP_ID":
                Secret.from_ssm_parameter(github_app_id),
                "DORPSGEK_GITHUB_APP_PRIVATE_KEY":
                Secret.from_ssm_parameter(github_app_private_key),
                "DORPSGEK_GITHUB_APP_SECRET":
                Secret.from_ssm_parameter(github_app_secret),
                "DORPSGEK_NICKSERV_PASSWORD":
                Secret.from_ssm_parameter(nickserv_password),
            },
            volumes={
                "/code/data":
                Volume(
                    name="data",
                    efs_volume_configuration=EfsVolumeConfiguration(
                        file_system_id=efs_seen.file_system_id, ),
                ),
                "/code/logs/ChannelLogger":
                Volume(
                    name="logs",
                    efs_volume_configuration=EfsVolumeConfiguration(
                        file_system_id=efs_logs.file_system_id, ),
                ),
            },
        )
Esempio n. 8
0
    def __init__(self, scope: Construct, id: str, *, deployment: Deployment,
                 policy: Policy, cluster: ICluster, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        Tags.of(self).add("Application", self.application_name)
        Tags.of(self).add("Deployment", deployment.value)

        policy.add_stack(self)

        if deployment == Deployment.PRODUCTION:
            desired_count = 2
            priority = 60
            dynamodb_prefix = "P-"
        else:
            desired_count = 1
            priority = 160
            dynamodb_prefix = "S-"

        sentry_dsn = parameter_store.add_secure_string(
            f"/MasterServerApi/{deployment.value}/SentryDSN").parameter

        self.container = ECSHTTPSContainer(
            self,
            self.application_name,
            subdomain_name=self.subdomain_name,
            deployment=deployment,
            policy=policy,
            application_name=self.application_name,
            image_name="ghcr.io/openttd/master-server",
            port=80,
            memory_limit_mib=96,
            desired_count=desired_count,
            cluster=cluster,
            priority=priority,
            command=[
                "--app",
                "web_api",
                "--bind",
                "0.0.0.0",
                "--db",
                "dynamodb",
                "--dynamodb-region",
                "eu-central-1",
                "--dynamodb-prefix",
                dynamodb_prefix,
            ],
            environment={
                "MASTER_SERVER_SENTRY_ENVIRONMENT": deployment.value.lower(),
            },
            secrets={
                "MASTER_SERVER_SENTRY_DSN":
                Secret.from_ssm_parameter(sentry_dsn),
            },
        )

        table_and_index = []
        for table in ("S-MSU-ip-port", "S-MSU-server", "P-MSU-ip-port",
                      "P-MSU-server"):
            table_and_index.extend([
                f"arn:aws:dynamodb:{self.region}:{self.account}:table/{table}",
                f"arn:aws:dynamodb:{self.region}:{self.account}:table/{table}/index/online_view",
                f"arn:aws:dynamodb:{self.region}:{self.account}:table/{table}/index/time_last_seen_view",
            ])

        self.container.task_role.add_to_policy(
            PolicyStatement(
                actions=[
                    "dynamodb:CreateTable",
                    "dynamodb:UpdateTimeToLive",
                    "dynamodb:PutItem",
                    "dynamodb:DescribeTable",
                    "dynamodb:ListTables",
                    "dynamodb:GetItem",
                    "dynamodb:Query",
                    "dynamodb:UpdateItem",
                ],
                resources=table_and_index,
            ))
Esempio n. 9
0
    def __init__(
        self,
        scope: App,
        id: str,
        envs: EnvSettings,
        components: ComponentsStack,
        base_resources: BaseResources,
    ):
        super().__init__(scope, id)

        self.db_secret_arn = Fn.import_value(
            BaseResources.get_database_secret_arn_output_export_name(envs))

        self.job_processing_queues = components.data_processing_queues
        self.vpc = base_resources.vpc
        self.db = base_resources.db

        self.app_bucket = Bucket(self, "App", versioned=True)

        if self.app_bucket.bucket_arn:
            CfnOutput(
                self,
                id="AppBucketOutput",
                export_name=self.get_app_bucket_arn_output_export_name(envs),
                value=self.app_bucket.bucket_arn,
            )

        self.pages_bucket = Bucket(self, "Pages", public_read_access=True)

        self.domain_name = StringParameter.from_string_parameter_name(
            self,
            "DomainNameParameter",
            string_parameter_name="/schema-cms-app/DOMAIN_NAME").string_value

        self.certificate_arn = StringParameter.from_string_parameter_name(
            self,
            "CertificateArnParameter",
            string_parameter_name="/schema-cms-app/CERTIFICATE_ARN"
        ).string_value

        django_secret = Secret(self,
                               "DjangoSecretKey",
                               secret_name="SCHEMA_CMS_DJANGO_SECRET_KEY")
        lambda_auth_token_secret = Secret(
            self,
            "LambdaAuthToken",
            secret_name="SCHEMA_CMS_LAMBDA_AUTH_TOKEN")

        if lambda_auth_token_secret.secret_arn:
            CfnOutput(
                self,
                id="lambdaAuthTokenArnOutput",
                export_name=self.get_lambda_auth_token_arn_output_export_name(
                    envs),
                value=lambda_auth_token_secret.secret_arn,
            )

        self.django_secret_key = EcsSecret.from_secrets_manager(django_secret)
        self.lambda_auth_token = EcsSecret.from_secrets_manager(
            lambda_auth_token_secret)

        tag_from_context = self.node.try_get_context("app_image_tag")
        tag = tag_from_context if tag_from_context != "undefined" else None

        api_image = ContainerImage.from_ecr_repository(
            repository=Repository.from_repository_name(
                self,
                id="BackendRepository",
                repository_name=BaseECR.get_backend_repository_name(envs)),
            tag=tag,
        )
        nginx_image = ContainerImage.from_ecr_repository(
            repository=Repository.from_repository_name(
                self,
                id="NginxRepository",
                repository_name=BaseECR.get_nginx_repository_name(envs)),
            tag=tag,
        )

        self.api = ApplicationLoadBalancedFargateService(
            self,
            "ApiService",
            service_name=f"{envs.project_name}-api-service",
            cluster=Cluster.from_cluster_attributes(
                self,
                id="WorkersCluster",
                cluster_name="schema-ecs-cluster",
                vpc=self.vpc,
                security_groups=[],
            ),
            task_image_options=ApplicationLoadBalancedTaskImageOptions(
                image=nginx_image,
                container_name="nginx",
                container_port=80,
                enable_logging=True,
            ),
            desired_count=1,
            cpu=512,
            memory_limit_mib=1024,
            certificate=Certificate.from_certificate_arn(
                self, "Cert", certificate_arn=self.certificate_arn),
            domain_name=self.domain_name,
            domain_zone=PrivateHostedZone(
                self,
                "zone",
                vpc=self.vpc,
                zone_name=self.domain_name,
            ),
        )

        self.api.task_definition.add_container(
            "backend",
            image=api_image,
            command=[
                "sh", "-c",
                "/bin/chamber exec $CHAMBER_SERVICE_NAME -- ./scripts/run.sh"
            ],
            logging=AwsLogDriver(stream_prefix="backend-container"),
            environment={
                "POSTGRES_DB": envs.data_base_name,
                "AWS_STORAGE_BUCKET_NAME": self.app_bucket.bucket_name,
                "AWS_STORAGE_PAGES_BUCKET_NAME": self.pages_bucket.bucket_name,
                "SQS_WORKER_QUEUE_URL":
                self.job_processing_queues[0].queue_url,
                "SQS_WORKER_EXT_QUEUE_URL":
                self.job_processing_queues[1].queue_url,
                "SQS_WORKER_MAX_QUEUE_URL":
                self.job_processing_queues[2].queue_url,
                "CHAMBER_SERVICE_NAME": "schema-cms-app",
                "CHAMBER_KMS_KEY_ALIAS": envs.project_name,
            },
            secrets={
                "DB_CONNECTION":
                EcsSecret.from_secrets_manager(
                    Secret.from_secret_arn(self,
                                           id="DbSecret",
                                           secret_arn=self.db_secret_arn)),
                "DJANGO_SECRET_KEY":
                self.django_secret_key,
                "LAMBDA_AUTH_TOKEN":
                self.lambda_auth_token,
            },
            cpu=512,
            memory_limit_mib=1024,
        )

        self.django_secret_key.grant_read(
            self.api.service.task_definition.task_role)

        self.app_bucket.grant_read_write(
            self.api.service.task_definition.task_role)
        self.pages_bucket.grant_read_write(
            self.api.service.task_definition.task_role)

        for queue in self.job_processing_queues:
            queue.grant_send_messages(
                self.api.service.task_definition.task_role)

        self.api.service.connections.allow_to(self.db.connections,
                                              Port.tcp(5432))
        self.api.task_definition.add_to_task_role_policy(
            PolicyStatement(
                actions=["ses:SendRawEmail", "ses:SendBulkTemplatedEmail"],
                resources=["*"],
            ))

        self.api.task_definition.add_to_task_role_policy(
            PolicyStatement(
                actions=[
                    "kms:Get*", "kms:Describe*", "kms:List*", "kms:Decrypt"
                ],
                resources=[
                    Fn.import_value(
                        BaseKMS.get_kms_arn_output_export_name(envs))
                ],
            ))

        self.api.task_definition.add_to_task_role_policy(
            PolicyStatement(actions=["ssm:DescribeParameters"],
                            resources=["*"]))

        self.api.task_definition.add_to_task_role_policy(
            PolicyStatement(
                actions=["ssm:GetParameters*"],
                resources=[
                    f"arn:aws:ssm:{self.region}:{self.account}:parameter/schema-cms-app/*"
                ],
            ))