This is an example CloudFormation stack with AWS CDK (Python) that can be used to set up the infrastructure for a Server-Side Google Tag Manager installation on AWS.
You can use the project to output CloudFormation template (cdk synth) or manage the whole deployment (cdk deploy).
I also wrote a blog post with more details about the project: Deploy Server-Side GTM on AWS ECS Fargate with CDK/CloudFormation.
The stack creates a Load balanced Fargate service on Elastic Container Service (ECS). Fargate is a serverless capacity type on AWS, meaning you no longer have to provision, configure, or scale clusters of virtual machines to run containers. This removes the need to choose server types, decide when to scale your clusters, or optimize cluster packing. You can simply select the amount of CPU and memory that you want for each instance and AWS will manage the hardware.
The stack creates a VPC (virtual private cloud) using 2 availability zones. Both zones have a public subnet and a private subnet. The load balancer will operate on the public subnets, being accessible to the public via the internet, while the Server-side GTM containers will run in the private subnets, accessible only to VPC services (e.g. the load balancer).
To reduce cost (~20€/month), the preview server shares the load balancer with the main servers but uses a different port.
To use this template you need:
- An AWS account and AWS CLI installed
- Python 3
- Google Tag Manager Server Container
- Registered domain name (optional but highly recommended)
$ python3 -m venv .venv
On MacOS or Linux:
$ source .venv/bin/activate
If you are a Windows platform, you would activate the virtualenv like this:
% .venv\Scripts\activate.bat
$ pip install -r requirements.txt
There are a number of context variables in cdk.json that can be used to tune the setup.
You can either define the variables in the file or add them as CLI parameters when
executing cdk commands.
| Variable | Description | Default value | 
|---|---|---|
| cpu | The amount of CPU per Fargate task (server instance). 256 = 0,25vCPU, 512 = 0,5vCPU etc. | 256 | 
| mem | The amount of memory per Fargate task (server instance). Minimum is 512 MiB | 512 | 
| desiredNodeCount | How many tasks (server instances) you wish to be running by default | 1 | 
| taskMaxCapacity | What is the maximum number of tasks (server instances) to run at a time | 2 | 
| taskMinCapacity | What is the least amount of tasks (server instances) to run at a time | 1 | 
| targetCpuUtilization | The average CPU utilization percentage that will trigger the cluster to add more tasks (server instances) | 70 | 
| containerConfig | The Container Configuration string for the GTM server container. Passed as CONTAINER_CONFIGenvironment variable to the GTM container image | null | 
| certificateArn | The ARN of an SSL certificate in AWS Certificate Manager for domain(used for HTTPS) | null(optional) | 
| domain | The domain name for the service. | null(optional) | 
| hostedZoneId | AWS Route53 Hosted Zone ID (if using AWS name servers with the domain) | null(optional) | 
| hostedZoneName | The Hosted Zone name (i.e. domain) | null(optional) | 
| natGateways | The number of NAT Gateways to deploy in the VPC. Can be lower than the number of AZs. | 2 (optional) | 
To use them in CLI:
cdk deploy -c cpu=512 -c containerConfig=XYZ123
The easies way to use a custom domain is to have your name servers hosted in AWS Route53. This is a very good choise especially if you use
AWS to also host the website(s) where you want to use the server-side GTM. In practice, you need a Hosted Zone either for the exact domain
you want to use, e.g. gtm.mydomain.com or for the domain above it, e.g. mydomain.com.
If you provide the hostedZoneId and hostedZoneName context variables, the stack will create an SSL certificate (for HTTPS) automatically.
Alternatively, you can create/upload the certificate manually in AWS Certificate Manager and add it to the certificateArn context variable.
For example, if you have a hosted zone for mydomain.com and you want the server-side GTM to use gtm.mydomain.com you could use the following
context variables:
cdk deploy -c domain=gtm.mydomain.com -c hostedZoneName=mydomain.com -c hostedZoneId=<Hosted Zone ID from AWS>
It's also possible to use external name servers and redirect your custom domain the server-side GTM with a CNAME DNS record.
In this case you should follow these steps:
- Create an SSL certificate in AWS Certificate Manager or upload your existing certificate there.
- You can do DNS validation by adding the CNAME record(s) to the domain with your existing name server provider
- Once the certificate is issued, copy the ARN from AWS management console
 
- Deploy the stack by adding domainandcertificateArncontext variables, e.g.
cdk deploy -c domain=gtm.mydomain.com -c certificateArn=<ARN for the certificate copied from AWS management console>
- Create a CNAMErecord for your domain and add the load balancer URL as the value.- The cdk deploycommand will write the load balancer URL in the terminal after deployment has finished
 
- The 
After installation and setting up the context variables in cdk.json, you can use following cdk commands:
- cdk lslist all stacks in the app
- cdk synthemits the synthesized CloudFormation template
- cdk deploydeploy this stack to your default AWS account/region
- cdk diffcompare deployed stack with current state
- cdk docsopen CDK documentation
If you use multiple profiles with you AWS CLI, you can add the --profile flag with the cdk command.
For example, I want to deploy the stack with min 3 and max 6 server instances using 0,5 vCPU and 1024 MiB per server
and a domain hosted in AWS Route53. I have a separate profile called sgtm for the AWS account, and I want to
deploy the stack in AWS region eu-west-1, I could use the following command:
CDK_DEFAULT_ACCOUNT='0123456789' CDK_DEFAULT_REGION='eu-west-1' cdk deploy \
    -c cpu=512 \
    -c mem=1024 \
    -c desiredNodeCount=3 \
    -c tasMinCapacity=3 \
    -c taskMaxCapacity=6 \
    -c domain=gtm.mydomain.com \
    -c hostedZoneId=ASDFGHJK123 \
    -c hostedZoneName=mydomain.com \
    --profile sgtm
Notice how you might also need to define the CDK_DEFAULT_ACCOUNT and CDK_DEFAULT_REGION environment variables.