Merge branch 'stable-3.1'

* stable-3.1:
  Use Gerrit version and config SHA1 to identify image tag
  Gerrit dual-master in HA recipe
  Allow customising cluster keys

Change-Id: I41b57fdb8070c567381a98255a673c8b0d0abe5f
diff --git a/README.md b/README.md
index 9c1a3ee..a453575 100644
--- a/README.md
+++ b/README.md
@@ -32,3 +32,4 @@
 
 * [Standalone Gerrit master sandbox with LDAP authentication](/single-master/README.md)
 * [Gerrit master and slave sandbox with LDAP authentication](/master-slave/README.md)
+* [Gerrit dual-master in HA sandbox with LDAP authentication](/multi-master/README.md)
diff --git a/dual-master/Makefile b/dual-master/Makefile
new file mode 100644
index 0000000..d5fc99a
--- /dev/null
+++ b/dual-master/Makefile
@@ -0,0 +1,152 @@
+include setup.env
+include ../version.env
+
+CLUSTER_TEMPLATE:=cf-cluster.yml
+SERVICE_MASTER_TEMPLATE:=cf-service-master.yml
+DNS_ROUTING_TEMPLATE:=cf-dns-route.yml
+AWS_FC_COMMAND=export AWS_PAGER=;aws cloudformation
+
+.PHONY: create-all delete-all \
+				cluster service-master-1 dns-routing \
+				wait-for-cluster-creation wait-for-service-master-1-creation wait-for-service-master-2-creation wait-for-dns-routing-creation \
+				wait-for-cluster-deletion wait-for-service-master-1-deletion wait-for-service-master-2-deletion wait-for-dns-routing-deletion \
+				gerrit-build gerrit-publish
+
+create-all: cluster wait-for-cluster-creation \
+						service-master-1 service-master-2 \
+						wait-for-service-master-1-creation wait-for-service-master-2-creation \
+						dns-routing wait-for-dns-routing-creation
+
+cluster:
+	$(AWS_FC_COMMAND) create-stack \
+		--stack-name $(CLUSTER_STACK_NAME) \
+		--capabilities CAPABILITY_IAM  \
+		--template-body file://`pwd`/$(CLUSTER_TEMPLATE) \
+		--region $(AWS_REGION) \
+		--parameters \
+		ParameterKey=DesiredCapacity,ParameterValue=$(CLUSTER_DESIRED_CAPACITY) \
+		ParameterKey=ECSKeyName,ParameterValue=$(CLUSTER_KEYS)
+
+service-master-1:
+	$(AWS_FC_COMMAND) create-stack \
+		--stack-name $(SERVICE_MASTER1_STACK_NAME) \
+		--capabilities CAPABILITY_IAM  \
+		--template-body file://`pwd`/$(SERVICE_MASTER_TEMPLATE) \
+		--region $(AWS_REGION) \
+		--parameters \
+		ParameterKey=ClusterStackName,ParameterValue=$(CLUSTER_STACK_NAME) \
+		ParameterKey=HostedZoneName,ParameterValue=$(HOSTED_ZONE_NAME) \
+		ParameterKey=Subdomain,ParameterValue=$(MASTER1_SUBDOMAIN) \
+		ParameterKey=DockerRegistryUrl,ParameterValue=$(DOCKER_REGISTRY_URI) \
+		ParameterKey=CertificateArn,ParameterValue=$(SSL_CERTIFICATE_ARN) \
+		ParameterKey=GerritKeyPrefix,ParameterValue=$(GERRIT_KEY_PREFIX)\
+		ParameterKey=DockerImage,ParameterValue=aws-gerrit/gerrit:$(IMAGE_TAG)
+
+service-master-2:
+	$(AWS_FC_COMMAND) create-stack \
+		--stack-name $(SERVICE_MASTER2_STACK_NAME) \
+		--capabilities CAPABILITY_IAM  \
+		--template-body file://`pwd`/$(SERVICE_MASTER_TEMPLATE) \
+		--region $(AWS_REGION) \
+		--parameters \
+		ParameterKey=ClusterStackName,ParameterValue=$(CLUSTER_STACK_NAME) \
+		ParameterKey=HostedZoneName,ParameterValue=$(HOSTED_ZONE_NAME) \
+		ParameterKey=Subdomain,ParameterValue=$(MASTER2_SUBDOMAIN) \
+		ParameterKey=DockerRegistryUrl,ParameterValue=$(DOCKER_REGISTRY_URI) \
+		ParameterKey=CertificateArn,ParameterValue=$(SSL_CERTIFICATE_ARN) \
+		ParameterKey=HTTPHostPort,ParameterValue=$(HTTP_HOST_PORT_MASTER2) \
+		ParameterKey=SSHHostPort,ParameterValue=$(SSH_HOST_PORT_MASTER2) \
+		ParameterKey=GerritKeyPrefix,ParameterValue=$(GERRIT_KEY_PREFIX)\
+		ParameterKey=DockerImage,ParameterValue=aws-gerrit/gerrit:$(IMAGE_TAG) \
+		ParameterKey=GerritDataVolume,ParameterValue=gerrit-data-master-2 \
+		ParameterKey=GerritIndexVolume,ParameterValue=gerrit-index-master-2 \
+		ParameterKey=GerritCacheVolume,ParameterValue=gerrit-cache-master-2 \
+		ParameterKey=GerritDbVolume,ParameterValue=gerrit-db-master-2 \
+		ParameterKey=GerritLogsVolume,ParameterValue=gerrit-logs-master-2
+
+dns-routing:
+	$(AWS_FC_COMMAND) create-stack \
+		--stack-name $(DNS_ROUTING_STACK_NAME) \
+		--capabilities CAPABILITY_IAM  \
+		--template-body file://`pwd`/$(DNS_ROUTING_TEMPLATE) \
+		--region $(AWS_REGION) \
+		--parameters \
+		ParameterKey=Master1ServiceStackName,ParameterValue=$(SERVICE_MASTER1_STACK_NAME) \
+		ParameterKey=Master2ServiceStackName,ParameterValue=$(SERVICE_MASTER2_STACK_NAME)
+
+wait-for-cluster-creation:
+	@echo "*** Wait for cluster stack '$(CLUSTER_STACK_NAME)' creation"
+	$(AWS_FC_COMMAND) wait stack-create-complete \
+	--stack-name $(CLUSTER_STACK_NAME) \
+	--region $(AWS_REGION)
+	@echo "*** Cluster stack '$(CLUSTER_STACK_NAME)' created"
+
+wait-for-service-master-1-creation:
+	@echo "*** Wait for service master stack '$(SERVICE_MASTER1_STACK_NAME)' creation"
+	$(AWS_FC_COMMAND) wait stack-create-complete \
+	--stack-name $(SERVICE_MASTER1_STACK_NAME) \
+	--region $(AWS_REGION)
+	@echo "*** Service stack '$(SERVICE_MASTER1_STACK_NAME)' created"
+
+wait-for-service-master-2-creation:
+	@echo "*** Wait for service master stack '$(SERVICE_MASTER2_STACK_NAME)' creation"
+	$(AWS_FC_COMMAND) wait stack-create-complete \
+	--stack-name $(SERVICE_MASTER2_STACK_NAME) \
+	--region $(AWS_REGION)
+	@echo "*** Service stack '$(SERVICE_MASTER2_STACK_NAME)' created"
+
+wait-for-dns-routing-creation:
+	@echo "*** Wait for DNS routing stack '$(DNS_ROUTING_STACK_NAME)' creation"
+	$(AWS_FC_COMMAND) wait stack-create-complete \
+	--stack-name $(DNS_ROUTING_STACK_NAME) \
+	--region $(AWS_REGION)
+	@echo "*** DNS Routing stack '$(DNS_ROUTING_STACK_NAME)' created"
+
+wait-for-cluster-deletion:
+	@echo "*** Wait for cluster stack '$(CLUSTER_STACK_NAME)' deletion"
+	$(AWS_FC_COMMAND) wait stack-delete-complete \
+	--stack-name $(CLUSTER_STACK_NAME) \
+	--region $(AWS_REGION)
+	@echo "*** Cluster stack '$(CLUSTER_STACK_NAME)' deleted"
+
+wait-for-service-master-deletion:
+	@echo "*** Wait for service master stack '$(SERVICE_MASTER_STACK_NAME)' deletion"
+	$(AWS_FC_COMMAND) wait stack-delete-complete \
+	--stack-name $(SERVICE_MASTER1_STACK_NAME) \
+	--region $(AWS_REGION)
+	@echo "*** Service stack master '$(SERVICE_MASTER_STACK_NAME)' deleted"
+
+wait-for-dns-routing-deletion:
+	@echo "*** Wait for DNS routing stack '$(DNS_ROUTING_STACK_NAME)' deletion"
+	$(AWS_FC_COMMAND) wait stack-delete-complete \
+	--stack-name $(DNS_ROUTING_STACK_NAME) \
+	--region $(AWS_REGION)
+	@echo "*** DNS routing stack '$(DNS_ROUTING_STACK_NAME)' deleted"
+
+delete-cluster:
+	$(AWS_FC_COMMAND) delete-stack \
+	--stack-name $(CLUSTER_STACK_NAME) \
+	--region $(AWS_REGION)
+
+delete-service-master-1:
+	$(AWS_FC_COMMAND) delete-stack \
+	--stack-name $(SERVICE_MASTER1_STACK_NAME) \
+	--region $(AWS_REGION)
+
+delete-service-master-2:
+	$(AWS_FC_COMMAND) delete-stack \
+	--stack-name $(SERVICE_MASTER2_STACK_NAME) \
+	--region $(AWS_REGION)
+
+delete-dns-routing:
+	$(AWS_FC_COMMAND) delete-stack \
+	--stack-name $(DNS_ROUTING_STACK_NAME) \
+	--region $(AWS_REGION)
+
+delete-all: delete-dns-routing wait-for-dns-routing-deletion \
+						delete-service-master-1 wait-for-service-master-1-deletion \
+						delete-service-master-2 wait-for-service-master-2-deletion \
+						delete-cluster wait-for-cluster-deletion
+
+gerrit-publish:
+	$(MAKE) -C ../gerrit gerrit-publish RECIPE=dual-master
diff --git a/dual-master/README.md b/dual-master/README.md
new file mode 100644
index 0000000..e8fc8d7
--- /dev/null
+++ b/dual-master/README.md
@@ -0,0 +1,209 @@
+# Gerrit dual-master in High-Availability
+
+This set of templates provides all the components to deploy a Gerrit dual-master
+in HA in ECS. The 2 masters will share the Git repositories via NFS, using EFS.
+
+## Architecture
+
+Four templates are provided in this example:
+* `cf-cluster`: define the ECS cluster and the networking stack
+* `cf-service-master-1`: define the service stack running Gerrit master 1
+* `cf-service-master-2`: define the service stack running Gerrit master 2
+* `cf-dns-route`: define the DNS routing for the service
+
+### Networking
+
+* Single VPC:
+ * CIDR: 10.0.0.0/16
+* Single Availability Zone
+* 1 public Subnets:
+ * CIDR: 10.0.0.0/24
+* 1 public NLB exposing:
+ * Gerrit master 1 HTTP on port 8080
+ * Gerrit master 1 SSH on port 29418
+* 1 public NLB exposing:
+ * Gerrit master 2 HTTP on port 8081
+ * Gerrit master 2 SSH on port 39418
+* 1 Internet Gateway
+* 2 type A alias DNS entry, for Gerrit master 1 and 2
+* A wildcard SSL certificate available in [AWS Certificate Manager](https://aws.amazon.com/certificate-manager/)
+
+### Data persistency
+
+* EBS volumes for:
+  * Indexes
+  * Caches
+  * Data
+
+### Deployment type
+
+* Latest Gerrit version deployed using the official [Docker image](https://hub.docker.com/r/gerritcodereview/gerrit)
+* Application deployed in ECS on a single EC2 instance
+
+### Logging
+
+* All the logs are forwarded to AWS CloudWatch in the LogGroup with the cluster
+  stack name
+
+## How to run it
+
+### Setup
+
+The `setup.env.template` is an example of setup file for the creation of the stacks.
+
+Before creating the stacks, create a `setup.env` in the `Makefile` directory and
+set the correct values of the environment variables.
+
+This is the list of available parameters:
+
+* `DOCKER_REGISTRY_URI`: Mandatory. URI of the Docker registry. See the
+  [prerequisites](#prerequisites) section for more details.
+* `SSL_CERTIFICATE_ARN`: Mandatory. ARN of the wildcard SSL Certificate, covering both master nodes.
+* `CLUSTER_STACK_NAME`: Optional. Name of the cluster stack. `gerrit-cluster` by default.
+* `SERVICE_MASTER1_STACK_NAME`: Optional. Name of the master 1 service stack. `gerrit-service-master-1` by default.
+* `SERVICE_MASTER2_STACK_NAME`: Optional. Name of the master 2 service stack. `gerrit-service-master-2` by default.
+* `DNS_ROUTING_STACK_NAME`: Optional. Name of the DNS routing stack. `gerrit-dns-routing` by default.
+* `HOSTED_ZONE_NAME`: Optional. Name of the hosted zone. `mycompany.com` by default.
+* `MASTER1_SUBDOMAIN`: Optional. Name of the master 1 sub domain. `gerrit-master-1-demo` by default.
+* `MASTER2_SUBDOMAIN`: Optional. Name of the master 2 sub domain. `gerrit-master-2-demo` by default.
+* `CLUSTER_DESIRED_CAPACITY`: Optional.  Number of EC2 instances composing the cluster. `1` by default.
+
+### Prerequisites
+
+The prerequisites to run this stack are:
+* a registered and correctly configured domain in
+[Route53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/getting-started.html)
+* to [publish the Docker image](#publish-custom-gerrit-docker-image) with your
+Gerrit configuration in AWS ECR
+* to [add Gerrit secrets](#add-gerrit-secrets-in-aws-secret-manager) in AWS Secret
+Manager
+* an SSL Certificate in AWS Certificate Manager (you can find more information on
+  how to create and handle certificates in AWS [here](https://aws.amazon.com/certificate-manager/getting-started/)
+
+### Add Gerrit Secrets in AWS Secret Manager
+
+[AWS Secret Manager](https://aws.amazon.com/secrets-manager/) is a secure way of
+storing and managing secrets of any type.
+
+The secrets you will have to add are the Gerrit SSH keys and the Register Email
+Private Key set in `secure.config`.
+
+#### SSH Keys
+
+The SSH keys you will need to add are the one usually created and used by Gerrit:
+* ssh_host_ecdsa_384_key
+* ssh_host_ecdsa_384_key.pub
+* ssh_host_ecdsa_521_key
+* ssh_host_ecdsa_521_key.pub
+* ssh_host_ecdsa_key
+* ssh_host_ecdsa_key.pub
+* ssh_host_ed25519_key
+* ssh_host_ed25519_key.pub
+* ssh_host_rsa_key
+* ssh_host_rsa_key.pub
+
+Plus a key used by the replication plugin:
+* replication_user_id_rsa
+* replication_user_id_rsa.pub
+
+You will have to create the keys and place them in a directory.
+
+#### Register Email Private Key
+
+You will need to create a secret and put it in a file called `registerEmailPrivateKey`
+in the same directory of the SSH keys.
+
+#### LDAP Password
+
+You will need to put the admin LDAP password in a file called `ldapPassword`
+in the same directory of the SSH keys.
+
+#### SMTP Password
+
+You will need to put the SMTP password in a file called `smtpPassword`
+in the same directory of the SSH keys.
+
+#### Import into AWS Secret Manager
+
+You can now run the [script](../gerrit/add_secrets_aws_secrets_manager.sh) to
+upload them to AWS Secret Manager:
+`add_secrets_aws_secrets_manager.sh /path/to/your/keys/directory`
+
+### Publish custom Gerrit Docker image
+
+* Create the repository in the Docker registry:
+  `aws ecr create-repository --repository-name aws-gerrit/gerrit`
+* Set the Docker registry URI in `DOCKER_REGISTRY_URI`
+* Create a `gerrit.setup` and set the correct parameters
+ * An example of the possible setting are in `gerrit.setup.template`
+ * The structure and parameters of `gerrit.setup` are the same as a normal `gerrit.config`
+ * Refer to the [Gerrit Configuration Documentation](https://gerrit-review.googlesource.com/Documentation/config-gerrit.html)
+   for the meaning of the parameters
+* Add the plugins you want to install in `./gerrit/plugins`
+* Publish the image: `make gerrit-publish`
+
+### Getting Started
+
+* Create a key pair to access the EC2 instances in the cluster:
+
+```
+aws ec2 create-key-pair --key-name gerrit-cluster-keys \
+  --query 'KeyMaterial' --output text > gerrit-cluster.pem
+```
+
+*NOTE: the EC2 key pair are useful when you need to connect to the EC2 instances
+for troubleshooting purposes. Store them in a `pem` file to use when ssh-ing into your
+instances as follow: `ssh -i yourKeyPairs.pem <ec2_instance_ip>`*
+
+* Create the cluster, services and DNS routing stacks:
+
+```
+make create-all
+```
+
+### Cleaning up
+
+```
+make delete-all
+```
+
+### Access your Gerrit instances
+
+Get the URL of your Gerrit master instances this way:
+
+```
+aws cloudformation describe-stacks \
+  --stack-name <SERVICE_MASTER1_STACK_NAME> \
+  | grep -A1 '"OutputKey": "CanonicalWebUrl"' \
+  | grep OutputValue \
+  | cut -d'"' -f 4
+
+aws cloudformation describe-stacks \
+  --stack-name <SERVICE_MASTER2_STACK_NAME> \
+  | grep -A1 '"OutputKey": "CanonicalWebUrl"' \
+  | grep OutputValue \
+  | cut -d'"' -f 4
+```
+
+Gerrit master instance ports:
+* HTTP `8080`
+* SSH `29418`
+
+# External services
+
+This is a list of external services that you might need to setup your stack and some suggestions
+on how to easily create them.
+
+## SMTP Server
+
+If you need to setup a SMTP service Amazon Simple Email Service can be used.
+Details how setup Amazon SES can be found [here](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-email-set-up.html).
+
+To correctly setup email notifications Gerrit requires ssl protocol on default port 465 to
+be enabled on SMTP Server. It is possible to setup Gerrit to talk to standard SMTP port 25
+but by default all EC2 instances are blocking it. To enable port 25 please follow [this](https://aws.amazon.com/premiumsupport/knowledge-center/ec2-port-25-throttle/) link.
+
+## LDAP Server
+
+If you need a testing LDAP server you can find details on how to easily
+create one in the [LDAP folder](../ldap/README.md).
diff --git a/dual-master/cf-cluster.yml b/dual-master/cf-cluster.yml
new file mode 100644
index 0000000..2f3c100
--- /dev/null
+++ b/dual-master/cf-cluster.yml
@@ -0,0 +1,330 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: A stack for deploying containerized applications onto a cluster of EC2
+             hosts using Elastic Container Service. This stack runs containers on
+             hosts that are in a public VPC subnet.
+Parameters:
+  DesiredCapacity:
+    Type: Number
+    Default: '1'
+    Description: Number of EC2 instances to launch in your ECS cluster.
+  MaxSize:
+    Type: Number
+    Default: '6'
+    Description: Maximum number of EC2 instances that can be launched in your ECS cluster.
+  ECSAMI:
+    Description: AMI ID
+    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
+    Default: /aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id
+  InstanceType:
+    Description: EC2 instance type
+    Type: String
+    Default: c4.xlarge
+    AllowedValues: [t2.micro, t2.small, t2.medium, t2.large, m3.medium, m3.large,
+      m3.xlarge, m3.2xlarge, m4.large, m4.xlarge, m4.2xlarge, m4.4xlarge, m4.10xlarge,
+      c4.large, c4.xlarge, c4.2xlarge, c4.4xlarge, c4.8xlarge, c3.large, c3.xlarge,
+      c3.2xlarge, c3.4xlarge, c3.8xlarge, r3.large, r3.xlarge, r3.2xlarge, r3.4xlarge,
+      r3.8xlarge, i2.xlarge, i2.2xlarge, i2.4xlarge, i2.8xlarge]
+    ConstraintDescription: Please choose a valid instance type.
+  ECSKeyName:
+    Type: String
+    Default: gerrit-cluster-keys
+    Description: EC2 key pair name the cluter's instances
+  EnvironmentName:
+      Description: An environment name used to build the log stream names
+      Type: String
+      Default: test
+Mappings:
+  # Hard values for the subnet masks. These masks define
+  # the range of internal IP addresses that can be assigned.
+  # The VPC can have all IP's from 10.0.0.0 to 10.0.255.255
+  # There is the subnet which cover the ranges:
+  #
+  # 10.0.0.0 - 10.0.0.255
+  SubnetConfig:
+    VPC:
+      CIDR: '10.0.0.0/16'
+    PublicOne:
+      CIDR: '10.0.0.0/24'
+Resources:
+  VPC:
+    Type: AWS::EC2::VPC
+    Properties:
+      EnableDnsSupport: true
+      EnableDnsHostnames: true
+      CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR']
+
+  # Public subnets, where containers can have public IP addresses
+  PublicSubnetOne:
+    Type: AWS::EC2::Subnet
+    Properties:
+      AvailabilityZone:
+         Fn::Select:
+         - 0
+         - Fn::GetAZs: {Ref: 'AWS::Region'}
+      VpcId: !Ref 'VPC'
+      CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR']
+      MapPublicIpOnLaunch: true
+
+  # Setup networking resources for the public subnets. Containers
+  # in the public subnets have public IP addresses and the routing table
+  # sends network traffic via the internet gateway.
+  InternetGateway:
+    Type: AWS::EC2::InternetGateway
+  GatewayAttachement:
+    Type: AWS::EC2::VPCGatewayAttachment
+    Properties:
+      VpcId: !Ref 'VPC'
+      InternetGatewayId: !Ref 'InternetGateway'
+  PublicRouteTable:
+    Type: AWS::EC2::RouteTable
+    Properties:
+      VpcId: !Ref 'VPC'
+  PublicRoute:
+    Type: AWS::EC2::Route
+    DependsOn: GatewayAttachement
+    Properties:
+      RouteTableId: !Ref 'PublicRouteTable'
+      DestinationCidrBlock: '0.0.0.0/0'
+      GatewayId: !Ref 'InternetGateway'
+  PublicSubnetOneRouteTableAssociation:
+    Type: AWS::EC2::SubnetRouteTableAssociation
+    Properties:
+      SubnetId: !Ref PublicSubnetOne
+      RouteTableId: !Ref PublicRouteTable
+
+  # ECS Resources
+  ECSCluster:
+    Type: AWS::ECS::Cluster
+
+  EcsHostSecurityGroup:
+    Type: AWS::EC2::SecurityGroup
+    Properties:
+      GroupDescription: Access to the ECS hosts that run containers
+      VpcId: !Ref 'VPC'
+      SecurityGroupIngress:
+          # Allow access to NLB from anywhere on the internet
+          - CidrIp: 0.0.0.0/0
+            IpProtocol: -1
+
+  CloudWatchLogsGroup:
+      Type: AWS::Logs::LogGroup
+      Properties:
+          LogGroupName: !Ref AWS::StackName
+          RetentionInDays: 14
+
+  # Autoscaling group. This launches the actual EC2 instances that will register
+  # themselves as members of the cluster, and run the docker containers.
+  ECSAutoScalingGroup:
+    Type: AWS::AutoScaling::AutoScalingGroup
+    Properties:
+      VPCZoneIdentifier:
+        - !Ref PublicSubnetOne
+      LaunchConfigurationName: !Ref 'ContainerInstances'
+      MinSize: '1'
+      MaxSize: !Ref 'MaxSize'
+      DesiredCapacity: !Ref 'DesiredCapacity'
+    CreationPolicy:
+      ResourceSignal:
+        Timeout: PT15M
+    UpdatePolicy:
+      AutoScalingReplacingUpdate:
+        WillReplace: 'true'
+  ContainerInstances:
+    Type: AWS::AutoScaling::LaunchConfiguration
+    Properties:
+      ImageId: !Ref 'ECSAMI'
+      SecurityGroups: [!Ref 'EcsHostSecurityGroup']
+      InstanceType: !Ref 'InstanceType'
+      IamInstanceProfile: !Ref 'EC2InstanceProfile'
+      KeyName: !Ref ECSKeyName
+      UserData:
+        Fn::Base64: !Sub |
+          #!/bin/bash -xe
+          echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config
+          # Make sure latest version of the helper scripts are installed as per recommendation:
+          # https://github.com/awsdocs/aws-cloudformation-user-guide/blob/master/doc_source/cfn-helper-scripts-reference.md#using-the-latest-version
+          yum install -y aws-cfn-bootstrap nfs-utils
+          # EFS setting
+          DIR_TGT=/mnt/efs/gerrit-shared
+          mkdir -p $DIR_TGT
+          EC2_REGION=${AWS::Region}
+          # 169.254.169.254 link-local address, valid only from the instance, to retrieve meta-data information.
+          EC2_AVAIL_ZONE=`curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone`
+          EFS_FILE_SYSTEM_ID=${FileSystem}
+          DIR_SRC=$EC2_AVAIL_ZONE.$EFS_FILE_SYSTEM_ID.efs.$EC2_REGION.amazonaws.com
+
+          touch /home/ec2-user/echo.res
+          echo $EFS_FILE_SYSTEM_ID >> /home/ec2-user/echo.res
+          echo $EC2_AVAIL_ZONE >> /home/ec2-user/echo.res
+          echo $EC2_REGION >> /home/ec2-user/echo.res
+          echo $DIR_SRC >> /home/ec2-user/echo.res
+          echo $DIR_TGT >> /home/ec2-user/echo.res
+          mount -t nfs4 -o nfsvers=4.1,hard,timeo=600,retrans=2 $DIR_SRC:/ $DIR_TGT >> /home/ec2-user/echo.res
+          mkdir $DIR_TGT/git
+          chown -R 1000:1000 $DIR_TGT
+          cp -p /etc/fstab /etc/fstab.back-$(date +%F)
+          echo -e \"$DIR_SRC:/ \t\t $DIR_TGT \t\t nfs \t\t defaults \t\t 0 \t\t 0\" | tee -a /etc/fstab
+          # Get the CloudWatch Logs agent
+          echo -e "
+            {\"logs\":
+              {\"logs_collected\":
+                {\"files\":
+                  {\"collect_list\":
+                    [
+                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master-2/_data/httpd_log\",
+                      \"log_group_name\": \"${AWS::StackName}\",
+                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master-2/httpd_log\",
+                      \"timezone\": \"UTC\"
+                      },
+                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master-2/_data/sshd_log\",
+                      \"log_group_name\": \"${AWS::StackName}\",
+                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master-2/sshd_log\",
+                      \"timezone\": \"UTC\"
+                      },
+                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master-2/_data/gc_log\",
+                      \"log_group_name\": \"${AWS::StackName}\",
+                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master-2/gc_log\",
+                      \"timezone\": \"UTC\"
+                      },
+                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master-2/_data/audit_log\",
+                      \"log_group_name\": \"${AWS::StackName}\",
+                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master-2/audit_log\",
+                      \"timezone\": \"UTC\"
+                      },
+                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master/_data/replication_log\",
+                      \"log_group_name\": \"${AWS::StackName}\",
+                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master/replication_log\",
+                      \"timezone\": \"UTC\"
+                      },
+                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master/_data/httpd_log\",
+                      \"log_group_name\": \"${AWS::StackName}\",
+                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master/httpd_log\",
+                      \"timezone\": \"UTC\"
+                      },
+                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master/_data/sshd_log\",
+                      \"log_group_name\": \"${AWS::StackName}\",
+                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master/sshd_log\",
+                      \"timezone\": \"UTC\"
+                      },
+                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master/_data/gc_log\",
+                      \"log_group_name\": \"${AWS::StackName}\",
+                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master/gc_log\",
+                      \"timezone\": \"UTC\"
+                      },
+                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master/_data/audit_log\",
+                      \"log_group_name\": \"${AWS::StackName}\",
+                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master/audit_log\",
+                      \"timezone\": \"UTC\"
+                      }
+                    ]
+                  }
+                }
+              }
+            }" >> /home/ec2-user/gerritlogsaccess.json
+          # Install the CloudWatch Logs agent
+          yum install -y wget
+          wget https://s3.amazonaws.com/amazoncloudwatch-agent/centos/amd64/latest/amazon-cloudwatch-agent.rpm
+          rpm -U ./amazon-cloudwatch-agent.rpm
+          /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/home/ec2-user/gerritlogsaccess.json -s
+          # Signal to CloudFormation aws-cfn-bootstrap has been correctly updated
+          /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region}
+  AutoscalingRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Statement:
+        - Effect: Allow
+          Principal:
+            Service: [application-autoscaling.amazonaws.com]
+          Action: ['sts:AssumeRole']
+      Path: /
+      Policies:
+      - PolicyName: service-autoscaling
+        PolicyDocument:
+          Statement:
+          - Effect: Allow
+            Action:
+              - 'application-autoscaling:*'
+              - 'cloudwatch:DescribeAlarms'
+              - 'cloudwatch:PutMetricAlarm'
+              - 'ecs:DescribeServices'
+              - 'ecs:UpdateService'
+            Resource: '*'
+  EC2InstanceProfile:
+    Type: AWS::IAM::InstanceProfile
+    Properties:
+      Path: /
+      Roles: [!Ref 'EC2Role']
+
+  # Role for the EC2 hosts. This allows the ECS agent on the EC2 hosts
+  # to communciate with the ECS control plane, as well as download the docker
+  # images from ECR to run on your host.
+  EC2Role:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Statement:
+        - Effect: Allow
+          Principal:
+            Service: [ec2.amazonaws.com]
+          Action: ['sts:AssumeRole']
+      Path: /
+      Policies:
+      - PolicyName: ecs-service
+        PolicyDocument:
+          Statement:
+          - Effect: Allow
+            Action:
+              - 'ecs:CreateCluster'
+              - 'ecs:DeregisterContainerInstance'
+              - 'ecs:DiscoverPollEndpoint'
+              - 'ecs:Poll'
+              - 'ecs:RegisterContainerInstance'
+              - 'ecs:StartTelemetrySession'
+              - 'ecs:Submit*'
+              - 'logs:CreateLogStream'
+              - 'logs:PutLogEvents'
+              - 'ecr:GetAuthorizationToken'
+              - 'ecr:BatchGetImage'
+              - 'ecr:GetDownloadUrlForLayer'
+            Resource: '*'
+  FileSystem:
+    Type: AWS::EFS::FileSystem
+    Properties:
+      FileSystemTags:
+        - Key: Name
+          Value: "multi-master-git-repo"
+  GitMountTarget:
+    Type: AWS::EFS::MountTarget
+    Properties:
+      FileSystemId: !Ref FileSystem
+      SubnetId: !Ref PublicSubnetOne
+      SecurityGroups:
+        - !Ref MountTargetSecurityGroup
+  MountTargetSecurityGroup:
+    Type: AWS::EC2::SecurityGroup
+    Properties:
+      VpcId: !Ref 'VPC'
+      GroupDescription: "Security group for mount target"
+      SecurityGroupIngress:
+        - IpProtocol: TCP
+          FromPort: 2049
+          ToPort: 2049
+          CidrIp: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR']
+
+Outputs:
+  ClusterName:
+    Description: The name of the ECS cluster
+    Value: !Ref 'ECSCluster'
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ClusterName' ] ]
+  VPCId:
+    Description: The ID of the VPC that this stack is deployed in
+    Value: !Ref 'VPC'
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'VPCId' ] ]
+  PublicSubnetOne:
+    Description: Public subnet one
+    Value: !Ref 'PublicSubnetOne'
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOne' ] ]
diff --git a/dual-master/cf-dns-route.yml b/dual-master/cf-dns-route.yml
new file mode 100644
index 0000000..c704f1f
--- /dev/null
+++ b/dual-master/cf-dns-route.yml
@@ -0,0 +1,59 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: A stack for the Gerrit service Route53 routing.
+Parameters:
+  Master1ServiceStackName:
+      Description: Stack name of the ECS Master Gerrit service
+      Type: String
+      Default: gerrit-service-master-1
+  Master2ServiceStackName:
+      Description: Stack name of the ECS Master Gerrit service
+      Type: String
+      Default: gerrit-service-master-2
+
+Resources:
+  Master1DnsRecord:
+      Type: AWS::Route53::RecordSet
+      Properties:
+        Name:
+          !Join
+            - '.'
+            - - Fn::ImportValue: !Join [':', [!Ref 'Master1ServiceStackName', 'Subdomain']]
+              - Fn::ImportValue: !Join [':', [!Ref 'Master1ServiceStackName', 'HostedZoneName']]
+        HostedZoneName:
+          !Join
+            - ''
+            - - Fn::ImportValue: !Join [':', [!Ref 'Master1ServiceStackName', 'HostedZoneName']]
+              - '.'
+        Comment: DNS name for Gerrit Master.
+        Type: A
+        AliasTarget:
+          DNSName:
+            Fn::ImportValue:
+              !Join [':', [!Ref 'Master1ServiceStackName', 'PublicLoadBalancerDNSName']]
+          HostedZoneId:
+            Fn::ImportValue:
+              !Join [':', [!Ref 'Master1ServiceStackName', 'CanonicalHostedZoneID']]
+          EvaluateTargetHealth: False
+  Master2DnsRecord:
+      Type: AWS::Route53::RecordSet
+      Properties:
+        Name:
+          !Join
+            - '.'
+            - - Fn::ImportValue: !Join [':', [!Ref 'Master2ServiceStackName', 'Subdomain']]
+              - Fn::ImportValue: !Join [':', [!Ref 'Master2ServiceStackName', 'HostedZoneName']]
+        HostedZoneName:
+          !Join
+            - ''
+            - - Fn::ImportValue: !Join [':', [!Ref 'Master2ServiceStackName', 'HostedZoneName']]
+              - '.'
+        Comment: DNS name for Gerrit Master.
+        Type: A
+        AliasTarget:
+          DNSName:
+            Fn::ImportValue:
+              !Join [':', [!Ref 'Master2ServiceStackName', 'PublicLoadBalancerDNSName']]
+          HostedZoneId:
+            Fn::ImportValue:
+              !Join [':', [!Ref 'Master2ServiceStackName', 'CanonicalHostedZoneID']]
+          EvaluateTargetHealth: False
diff --git a/dual-master/cf-service-master.yml b/dual-master/cf-service-master.yml
new file mode 100644
index 0000000..29b4322
--- /dev/null
+++ b/dual-master/cf-service-master.yml
@@ -0,0 +1,324 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Deploy a service into an ECS cluster behind a public load balancer.
+Parameters:
+  GerritServiceName:
+    Type: String
+    Default: gerrit-master
+  SlaveServiceStackName:
+    Type: String
+    Default: gerrit-slave
+  ClusterStackName:
+      Description: Stack name of the ECS cluster to deply the serivces
+      Type: String
+      Default: gerrit-cluster
+  EnvironmentName:
+      Description: An environment name used to build the log stream names
+      Type: String
+      Default: test
+  DockerImage:
+        Description: Gerrit official Docker image
+        Type: String
+        Default: aws-gerrit/gerrit:latest
+  GerritLBDockerImage:
+        Description: Load Balancer official Docker image
+        Type: String
+        Default: haproxy:1.7
+  DockerRegistryUrl:
+        Description: Docker registry URL
+        Type: String
+  DesiredCount:
+        Description: How many instances of this task should we run across our cluster?
+        Type: Number
+        Default: 1
+  HTTPHostPort:
+        Description: Gerrit HTTP port
+        Type: Number
+        Default: 8080
+  HTTPContainerPort:
+        Description: Gerrit HTTP port
+        Type: Number
+        Default: 8080
+  HTTPSHostPort:
+        Description: Gerrit HTTPS port
+        Type: Number
+        Default: 443
+  SSHHostPort:
+        Description: Gerrit SSH port
+        Type: Number
+        Default: 29418
+  SSHContainerPort:
+        Description: Gerrit SSH port
+        Type: Number
+        Default: 29418
+  CertificateArn:
+        Description: SSL Certificates ARN
+        Type: String
+  HostedZoneName:
+        Description: The route53 HostedZoneName.
+        Type: String
+  Subdomain:
+        Description: The subdomain of the Gerrit cluster
+        Type: String
+        Default: gerrit-master-demo
+  GerritKeyPrefix:
+        Description: Gerrit credentials keys prefix
+        Type: String
+  GerritGitVolume:
+      Description: Gerrit git volume name
+      Type: String
+      Default: gerrit-git-master
+  GerritDataVolume:
+      Description: Gerrit data volume name
+      Type: String
+      Default: gerrit-data-master
+  GerritIndexVolume:
+      Description: Gerrit index volume name
+      Type: String
+      Default: gerrit-index-master
+  GerritCacheVolume:
+      Description: Gerrit cache volume name
+      Type: String
+      Default: gerrit-cache-master
+  GerritDbVolume:
+      Description: Gerrit db volume name
+      Type: String
+      Default: gerrit-db-master
+  GerritLogsVolume:
+      Description: Gerrit logs volume name
+      Type: String
+      Default: gerrit-logs-master
+
+Resources:
+    Service:
+        Type: AWS::ECS::Service
+        DependsOn:
+          - HTTPListener
+          - SSHListener
+        Properties:
+            Cluster:
+              Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'ClusterName']]
+            DesiredCount: !Ref DesiredCount
+            TaskDefinition: !Ref TaskDefinition
+            LoadBalancers:
+                - ContainerName: !Ref GerritServiceName
+                  ContainerPort: !Ref HTTPContainerPort
+                  TargetGroupArn: !Ref HTTPTargetGroup
+                - ContainerName: !Ref GerritServiceName
+                  ContainerPort: !Ref SSHContainerPort
+                  TargetGroupArn: !Ref SSHTargetGroup
+
+    TaskDefinition:
+        Type: AWS::ECS::TaskDefinition
+        Properties:
+            Family: !Join ['', [!Ref GerritServiceName, TaskDefinition]]
+            TaskRoleArn: !Ref ECSTaskExecutionRole
+            ExecutionRoleArn: !Ref ECSTaskExecutionRole
+            NetworkMode: bridge
+            ContainerDefinitions:
+                - Name: !Ref GerritServiceName
+                  Essential: true
+                  Image: !Sub '${DockerRegistryUrl}/${DockerImage}'
+                  Environment:
+                    - Name: CANONICAL_WEB_URL
+                      Value: !Sub 'https://${Subdomain}.${HostedZoneName}'
+                    - Name: HTTPD_LISTEN_URL
+                      Value: !Sub 'proxy-https://*:${HTTPContainerPort}/'
+                    - Name: AWS_REGION
+                      Value: !Ref AWS::Region
+                    - Name: SETUP_REPLICATION
+                      Value: false
+                    - Name: GERRIT_KEY_PREFIX
+                      Value: !Ref GerritKeyPrefix
+                  MountPoints:
+                    - SourceVolume: !Ref GerritGitVolume
+                      ContainerPath: /var/gerrit/git
+                    - SourceVolume: !Ref GerritDataVolume
+                      ContainerPath: /var/gerrit/data
+                    - SourceVolume: !Ref GerritIndexVolume
+                      ContainerPath: /var/gerrit/index
+                    - SourceVolume: !Ref GerritCacheVolume
+                      ContainerPath: /var/gerrit/cache
+                    - SourceVolume: !Ref GerritDbVolume
+                      ContainerPath: /var/gerrit/db
+                    - SourceVolume: !Ref GerritLogsVolume
+                      ContainerPath: /var/gerrit/logs
+                  Cpu: 1024
+                  Memory: 2048
+                  PortMappings:
+                    - ContainerPort: !Ref HTTPContainerPort
+                      HostPort: !Ref HTTPHostPort
+                      Protocol: tcp
+                    - ContainerPort: !Ref SSHContainerPort
+                      HostPort: !Ref SSHHostPort
+                      Protocol: tcp
+                  LogConfiguration:
+                    LogDriver: awslogs
+                    Options:
+                        awslogs-group: !Ref ClusterStackName
+                        awslogs-region: !Ref AWS::Region
+                        awslogs-stream-prefix: !Ref EnvironmentName
+            Volumes:
+              - Name: !Ref 'GerritGitVolume'
+                Host:
+                  SourcePath: "/mnt/efs/gerrit-shared/git"
+              - Name: !Ref 'GerritDbVolume'
+                DockerVolumeConfiguration:
+                  Scope: shared
+                  Autoprovision: true
+                  Driver: local
+                  Labels:
+                    gerrit-db: !Join ['-', [!Ref EnvironmentName, !Ref GerritDbVolume]]
+              - Name: !Ref 'GerritDataVolume'
+                DockerVolumeConfiguration:
+                  Scope: shared
+                  Autoprovision: true
+                  Driver: local
+                  Labels:
+                    gerrit-data: !Join ['-', [!Ref EnvironmentName, !Ref GerritDataVolume]]
+              - Name: !Ref 'GerritCacheVolume'
+                DockerVolumeConfiguration:
+                  Scope: shared
+                  Autoprovision: true
+                  Driver: local
+                  Labels:
+                    gerrit-cache: !Join ['-', [!Ref EnvironmentName, !Ref GerritCacheVolume]]
+              - Name: !Ref 'GerritIndexVolume'
+                DockerVolumeConfiguration:
+                  Scope: shared
+                  Autoprovision: true
+                  Driver: local
+                  Labels:
+                    gerrit-index: !Join ['-', [!Ref EnvironmentName, !Ref GerritIndexVolume]]
+              - Name: !Ref 'GerritLogsVolume'
+                DockerVolumeConfiguration:
+                  Scope: shared
+                  Autoprovision: true
+                  Driver: local
+                  Labels:
+                    gerrit-logs: !Join ['-', [!Ref EnvironmentName, !Ref GerritLogsVolume]]
+
+
+    LoadBalancer:
+        Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+        Properties:
+            Type: network
+            Scheme: internet-facing
+            Subnets:
+              - Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetOne']]
+            Tags:
+                - Key: Name
+                  Value: !Join ['-', [!Ref 'EnvironmentName', !Ref 'GerritServiceName', 'nlb']]
+
+    HTTPTargetGroup:
+        Type: AWS::ElasticLoadBalancingV2::TargetGroup
+        DependsOn: LoadBalancer
+        Properties:
+            VpcId:
+              Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
+            Port: !Ref HTTPHostPort
+            Protocol: TCP
+
+    HTTPListener:
+        Type: AWS::ElasticLoadBalancingV2::Listener
+        DependsOn: LoadBalancer
+        Properties:
+            Certificates:
+              - CertificateArn: !Ref CertificateArn
+            DefaultActions:
+            - Type: forward
+              TargetGroupArn: !Ref HTTPTargetGroup
+            LoadBalancerArn: !Ref LoadBalancer
+            Port: !Ref HTTPSHostPort
+            Protocol: TLS
+
+    SSHTargetGroup:
+        Type: AWS::ElasticLoadBalancingV2::TargetGroup
+        DependsOn: LoadBalancer
+        Properties:
+            VpcId:
+              Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
+            Port: !Ref SSHHostPort
+            Protocol: TCP
+
+    SSHListener:
+        Type: AWS::ElasticLoadBalancingV2::Listener
+        DependsOn: LoadBalancer
+        Properties:
+            DefaultActions:
+            - Type: forward
+              TargetGroupArn: !Ref SSHTargetGroup
+            LoadBalancerArn: !Ref LoadBalancer
+            Port: !Ref SSHHostPort
+            Protocol: TCP
+
+    # This is a role which is used by the ECS tasks themselves.
+    ECSTaskExecutionRole:
+      Type: AWS::IAM::Role
+      Properties:
+        AssumeRolePolicyDocument:
+          Statement:
+          - Effect: Allow
+            Principal:
+              Service: [ecs-tasks.amazonaws.com]
+            Action: ['sts:AssumeRole']
+        Path: /
+        Policies:
+          - PolicyName: AmazonECSTaskExecutionRolePolicy
+            PolicyDocument:
+              Statement:
+              - Effect: Allow
+                Action:
+                  # Allow the ECS Tasks to download images from ECR
+                  - 'ecr:GetAuthorizationToken'
+                  - 'ecr:BatchCheckLayerAvailability'
+                  - 'ecr:GetDownloadUrlForLayer'
+                  - 'ecr:BatchGetImage'
+                  # Allow the ECS tasks to upload logs to CloudWatch
+                  - 'logs:CreateLogStream'
+                  - 'logs:PutLogEvents'
+                Resource: '*'
+          - PolicyName: AmazonECSTaskSecretManagerRolePolicy
+            PolicyDocument:
+              Statement:
+              - Effect: Allow
+                Action:
+                  # Allow the ECS Tasks to get SSH Keys
+                  - 'secretsmanager:GetSecretValue'
+                  - 'kms:Decrypt'
+                Resource: '*'
+
+Outputs:
+  PublicLoadBalancerDNSName:
+    Description: The DNS name of the external load balancer
+    Value: !GetAtt 'LoadBalancer.DNSName'
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicLoadBalancerDNSName' ] ]
+  CanonicalHostedZoneID:
+    Description: Canonical Hosted Zone ID
+    Value: !GetAtt 'LoadBalancer.CanonicalHostedZoneID'
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'CanonicalHostedZoneID' ] ]
+  PublicLoadBalancerUrl:
+    Description: The url of the external load balancer
+    Value: !Join ['', ['http://', !GetAtt 'LoadBalancer.DNSName']]
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicLoadBalancerUrl' ] ]
+  HostedZoneName:
+    Description: Route53 Hosted Zone name
+    Value: !Ref HostedZoneName
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'HostedZoneName' ] ]
+  Subdomain:
+    Description: Service DNS subdomain
+    Value: !Ref Subdomain
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'Subdomain' ] ]
+  CanonicalWebUrl:
+    Description: Canonical Web URL
+    Value: !Sub 'https://${Subdomain}.${HostedZoneName}'
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'CanonicalWebUrl' ] ]
diff --git a/dual-master/setup.env.template b/dual-master/setup.env.template
new file mode 100644
index 0000000..548a7f8
--- /dev/null
+++ b/dual-master/setup.env.template
@@ -0,0 +1,14 @@
+CLUSTER_STACK_NAME:=gerrit-cluster
+CLUSTER_KEYS:=gerrit-cluster-keys
+CLUSTER_DESIRED_CAPACITY:=1
+SERVICE_MASTER1_STACK_NAME:=gerrit-service-master-1
+SERVICE_MASTER2_STACK_NAME:=gerrit-service-master-2
+HTTP_HOST_PORT_MASTER2:=8081
+SSH_HOST_PORT_MASTER2:=29419
+DNS_ROUTING_STACK_NAME:=gerrit-dns-routing
+HOSTED_ZONE_NAME:=yourcompany.com
+MASTER1_SUBDOMAIN:=gerrit-master-1.gerrit-demo
+MASTER2_SUBDOMAIN:=gerrit-master-2.gerrit-demo
+AWS_REGION:=us-east-1
+DOCKER_REGISTRY_URI:=<yourAccountId>.dkr.ecr.us-east-1.amazonaws.com
+SSL_CERTIFICATE_ARN=arn:aws:acm:us-east-1:<yourAccountId>:certificate/33e2c235-a4d1-42b7-b866-18d8d744975c
diff --git a/gerrit/Dockerfile b/gerrit/Dockerfile
index f8bfcd5..aa03945 100644
--- a/gerrit/Dockerfile
+++ b/gerrit/Dockerfile
@@ -1,4 +1,4 @@
-FROM gerritcodereview/gerrit:3.2.0-rc3-centos8
+FROM gerritcodereview/gerrit:$GERRIT_VERSION.$GERRIT_PATCH-centos8
 
 USER root
 
diff --git a/gerrit/Makefile b/gerrit/Makefile
index 8440236..7430ef5 100644
--- a/gerrit/Makefile
+++ b/gerrit/Makefile
@@ -1,8 +1,5 @@
 include ../$(RECIPE)/setup.env
-
-GERRIT_BRANCH=stable-3.2
-GERRIT_CI=https://gerrit-ci.gerritforge.com/view/Plugins-$(GERRIT_BRANCH)/job
-LAST_BUILD=lastSuccessfulBuild/artifact/bazel-bin/plugins
+include ../version.env
 
 docker-registry-login:
 	aws ecr get-login-password --region $(AWS_REGION) \
@@ -22,8 +19,10 @@
 	|| { echo >&2 "Cannot download javamelody plugin: Check internet connection. Aborting"; exit 1; }
 
 gerrit-build:
-	docker build -t aws-gerrit/gerrit .
-	docker tag aws-gerrit/gerrit:latest $(DOCKER_REGISTRY_URI)/aws-gerrit/gerrit:latest
+	cat Dockerfile | \
+		GERRIT_VERSION=$(GERRIT_VERSION) GERRIT_PATCH=$(GERRIT_PATCH) envsubst | \
+		docker build -f - -t aws-gerrit/gerrit:$(IMAGE_TAG) .
+	docker tag aws-gerrit/gerrit:$(IMAGE_TAG) $(DOCKER_REGISTRY_URI)/aws-gerrit/gerrit:$(IMAGE_TAG)
 
 gerrit-publish: gerrit-get-plugins docker-registry-login gerrit-build
-	docker push $(DOCKER_REGISTRY_URI)/aws-gerrit/gerrit:latest
+	docker push $(DOCKER_REGISTRY_URI)/aws-gerrit/gerrit:$(IMAGE_TAG)
diff --git a/gerrit/entrypoint.sh b/gerrit/entrypoint.sh
index 0c50ef3..5b1ed7e 100755
--- a/gerrit/entrypoint.sh
+++ b/gerrit/entrypoint.sh
@@ -5,10 +5,11 @@
 git config -f /var/gerrit/etc/gerrit.config httpd.listenUrl "${HTTPD_LISTEN_URL:-http://*:8080/}"
 git config -f /var/gerrit/etc/gerrit.config container.slave "${CONTAINER_SLAVE:-false}"
 
-if [ $CONTAINER_SLAVE ]
-then
+if [ $CONTAINER_SLAVE ]; then
   rm -fr /var/gerrit/plugins/replication.jar
   java -jar /var/gerrit/bin/gerrit.war reindex --index groups
+elif [ -f /var/gerrit/index/gerrit_index.config ]; then
+  java -jar /var/gerrit/bin/gerrit.war reindex -d /var/gerrit
 else
   java -jar /var/gerrit/bin/gerrit.war init --no-auto-start --batch --install-all-plugins -d /var/gerrit
 fi
diff --git a/master-slave/Makefile b/master-slave/Makefile
index ee5ec6f..85b420c 100644
--- a/master-slave/Makefile
+++ b/master-slave/Makefile
@@ -1,4 +1,5 @@
 include setup.env
+include ../version.env
 
 CLUSTER_TEMPLATE:=cf-cluster.yml
 SERVICE_MASTER_TEMPLATE:=cf-service-master.yml
@@ -24,7 +25,8 @@
 		--template-body file://`pwd`/$(CLUSTER_TEMPLATE) \
 		--region $(AWS_REGION) \
 		--parameters \
-		ParameterKey=DesiredCapacity,ParameterValue=$(CLUSTER_DESIRED_CAPACITY)
+		ParameterKey=DesiredCapacity,ParameterValue=$(CLUSTER_DESIRED_CAPACITY) \
+		ParameterKey=ECSKeyName,ParameterValue=$(CLUSTER_KEYS)
 
 service-master:
 	$(AWS_FC_COMMAND) create-stack \
@@ -39,7 +41,8 @@
 		ParameterKey=DockerRegistryUrl,ParameterValue=$(DOCKER_REGISTRY_URI) \
 		ParameterKey=CertificateArn,ParameterValue=$(SSL_CERTIFICATE_ARN) \
 		ParameterKey=SlaveServiceStackName,ParameterValue=$(SERVICE_SLAVE_STACK_NAME) \
-		ParameterKey=GerritKeyPrefix,ParameterValue=$(GERRIT_KEY_PREFIX)
+		ParameterKey=GerritKeyPrefix,ParameterValue=$(GERRIT_KEY_PREFIX)\
+		ParameterKey=DockerImage,ParameterValue=aws-gerrit/gerrit:$(IMAGE_TAG)
 
 service-slave:
 	$(AWS_FC_COMMAND) create-stack \
@@ -53,7 +56,8 @@
 		ParameterKey=Subdomain,ParameterValue=$(SLAVE_SUBDOMAIN) \
 		ParameterKey=DockerRegistryUrl,ParameterValue=$(DOCKER_REGISTRY_URI) \
 		ParameterKey=CertificateArn,ParameterValue=$(SSL_CERTIFICATE_ARN) \
-		ParameterKey=GerritKeyPrefix,ParameterValue=$(GERRIT_KEY_PREFIX)
+		ParameterKey=GerritKeyPrefix,ParameterValue=$(GERRIT_KEY_PREFIX)\
+		ParameterKey=DockerImage,ParameterValue=aws-gerrit/gerrit:$(IMAGE_TAG)
 
 dns-routing:
 	$(AWS_FC_COMMAND) create-stack \
diff --git a/master-slave/setup.env.template b/master-slave/setup.env.template
index eb20cf5..2d29f51 100644
--- a/master-slave/setup.env.template
+++ b/master-slave/setup.env.template
@@ -1,4 +1,5 @@
 CLUSTER_STACK_NAME:=gerrit-cluster
+CLUSTER_KEYS:=gerrit-cluster-keys
 CLUSTER_DESIRED_CAPACITY:=1
 SERVICE_MASTER_STACK_NAME:=gerrit-service-master
 SERVICE_SLAVE_STACK_NAME:=gerrit-service-slave
diff --git a/single-master/Makefile b/single-master/Makefile
index 4db2552..b440db7 100644
--- a/single-master/Makefile
+++ b/single-master/Makefile
@@ -1,4 +1,5 @@
 include setup.env
+include ../version.env
 
 CLUSTER_TEMPLATE:=cf-cluster.yml
 SERVICE_TEMPLATE:=cf-service.yml
@@ -20,7 +21,9 @@
 		--stack-name $(CLUSTER_STACK_NAME) \
 		--capabilities CAPABILITY_IAM  \
 		--template-body file://`pwd`/$(CLUSTER_TEMPLATE) \
-		--region $(AWS_REGION)
+		--region $(AWS_REGION) \
+		--parameters \
+		ParameterKey=ECSKeyName,ParameterValue=$(CLUSTER_KEYS)
 
 service:
 	$(AWS_FC_COMMAND) create-stack \
@@ -34,7 +37,8 @@
 		ParameterKey=Subdomain,ParameterValue=$(SUBDOMAIN) \
 		ParameterKey=DockerRegistryUrl,ParameterValue=$(DOCKER_REGISTRY_URI) \
 		ParameterKey=CertificateArn,ParameterValue=$(SSL_CERTIFICATE_ARN) \
-		ParameterKey=GerritKeyPrefix,ParameterValue=$(GERRIT_KEY_PREFIX)
+		ParameterKey=GerritKeyPrefix,ParameterValue=$(GERRIT_KEY_PREFIX) \
+		ParameterKey=DockerImage,ParameterValue=aws-gerrit/gerrit:$(IMAGE_TAG) \
 
 dns-routing:
 	$(AWS_FC_COMMAND) create-stack \
diff --git a/single-master/setup.env.template b/single-master/setup.env.template
index 63a6891..25b1134 100644
--- a/single-master/setup.env.template
+++ b/single-master/setup.env.template
@@ -1,5 +1,6 @@
 CLUSTER_STACK_NAME:=gerrit-cluster
 SERVICE_STACK_NAME:=gerrit-service
+CLUSTER_KEYS:=gerrit-cluster-keys
 DNS_ROUTING_STACK_NAME:=gerrit-dns-routing
 HOSTED_ZONE_NAME:=mycompany.com
 SUBDOMAIN:=gerrit-master-demo
diff --git a/version.env b/version.env
new file mode 100644
index 0000000..975b4dc
--- /dev/null
+++ b/version.env
@@ -0,0 +1,7 @@
+GERRIT_VERSION=3.2
+GERRIT_PATCH=0-rc3
+GERRIT_BRANCH=stable-$(GERRIT_VERSION)
+GERRIT_CI=https://gerrit-ci.gerritforge.com/view/Plugins-$(GERRIT_BRANCH)/job
+LAST_BUILD=lastSuccessfulBuild/artifact/bazel-bin/plugins
+HEAD_SHA1=$(shell git log -1 --format=format:%h)
+IMAGE_TAG=$(GERRIT_VERSION).$(GERRIT_PATCH)-$(HEAD_SHA1)