AWS Fargate deployment guide

A step-by-step guide for an example deployment of Tines using the AWS CLI to create all of the necessary ECS/Fargate, RDS, Elasticache and ALB resources etc.

1. Prepare an SSL certificate 

Following the instructions at https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html, create a certificate for the domain that you ultimately want your Tines instance to be accessible at in your browser.

For this step, it's easiest to just use the AWS console and follow the instructions for validation. The remaining steps will use the CLI!

2. Prepare the Tines Docker image 

For this step you'll need access to the tines-app and tines-nginx Docker Hub repositories. If you don't have access then the Tines support team can provide it.

To make things a little easier later on, we'll create an AWS ECR repository and copy the image from Docker Hub into that repository:

aws ecr create-repository --repository-name tines-app

# Replace this with the address of the registry output in the previous command:
REGISTRY=306378194054.dkr.ecr.eu-west-1.amazonaws.com

aws ecr get-login-password --region eu-west-1 | \
  docker login --username AWS --password-stdin $REGISTRY

# Replace this with the name of the latest Tines image:
IMAGE=tines-app:3c8e6560_v10_3_1

docker pull tines/$IMAGE
docker tag tines/$IMAGE $REGISTRY/$IMAGE
docker push $REGISTRY/$IMAGE

3. Prepare some security groups 

To more clearly illustrate how the different components talk to each other, we'll create a security group for each one and open only the necessary ports:

aws ec2 create-security-group \
  --group-name tines-lb \
  --description "Load balancer security group for the Tines application"
aws ec2 create-security-group \
  --group-name tines-db \
  --description "Database security group for the Tines application"
aws ec2 create-security-group \
  --group-name tines-redis \
  --description "Redis security group for the Tines application"
aws ec2 create-security-group \
  --group-name tines-app \
  --description "tines-app container security group for the Tines application"
aws ec2 create-security-group \
  --group-name tines-sidekiq \
  --description "tines-sidekiq container security group for the Tines application"

aws ec2 authorize-security-group-ingress \
  --group-name tines-lb --protocol tcp --port 443 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
  --group-name tines-app --source-group tines-lb --protocol tcp --port 3000
aws ec2 authorize-security-group-ingress \
  --group-name tines-db --source-group tines-app --protocol tcp --port 5432
aws ec2 authorize-security-group-ingress \
  --group-name tines-db --source-group tines-sidekiq --protocol tcp --port 5432
aws ec2 authorize-security-group-ingress \
  --group-name tines-redis --source-group tines-sidekiq --protocol tcp --port 6379
aws ec2 authorize-security-group-ingress \
  --group-name tines-redis --source-group tines-app --protocol tcp --port 6379

Note that running the above commands also populates the following default outbound rule for each security group:

4. Create a Postgres database 

# Replace this with the ID of the tines-db security group created in step 3:
DB_SECURITY_GROUP_ID=sg-031793e3657d99de4

# Enter a password at the prompt after running this command.
# A value that contains punctuation other than underscores and dashes may cause errors.
read -rs DB_PASSWORD

aws rds create-db-instance \
  --db-instance-identifier tines \
  --db-instance-class db.t3.micro \
  --engine postgres \
  --engine-version 11.14 \
  --master-username tines \
  --master-user-password $DB_PASSWORD \
  --allocated-storage 10 \
  --db-name tines \
  --vpc-security-group-ids $DB_SECURITY_GROUP_ID

5. Create a Redis cluster 

# Replace these with the IDs of two subnets from your VPC:
SUBNET_IDS="subnet-606e7706,subnet-bd5bb2f6"

# Replace this with the ID of the tines-redis security group created in step 3:
REDIS_SECURITY_GROUP_ID=sg-03b5ab812cb193c78

aws elasticache create-cache-subnet-group \
  --cache-subnet-group-name tines-redis \
  --cache-subnet-group-description "Tines Redis" \
  --subnet-ids $SUBNET_IDS

aws elasticache create-cache-cluster \
  --cache-cluster-id tines \
  --engine redis \
  --engine-version 6.x \
  --cache-node-type cache.t3.micro \
  --num-cache-nodes 1 \
  --cache-subnet-group-name tines-redis \
  --security-group-ids $REDIS_SECURITY_GROUP_ID

6. Create a load balancer 

# Replace these with the IDs of two subnets from your VPC:
SUBNET_IDS="subnet-606e7706,subnet-bd5bb2f6"

# Replace this with the ID of the tines-lb security group created in step 3:
LB_SECURITY_GROUP_ID=sg-0d61adeef4916712a

aws elbv2 create-load-balancer \
  --name tines \
  --subnets $SUBNET_IDS \
  --security-groups $LB_SECURITY_GROUP_ID

# Replace this with the ARN of the load balancer created by the previous command:
LOAD_BALANCER_ARN=arn:aws:elasticloadbalancing:eu-west-1:306378194054:loadbalancer/app/tines/ad446b3207c26fe7

# Replace this with the ID of your VPC:
VPC_ID="vpc-1f66ca66"

aws elbv2 create-target-group \
  --name tines-app \
  --protocol HTTP \
  --port 3000 \
  --target-type ip \
  --health-check-path '/is_up' \
  --vpc-id $VPC_ID

# Replace this with the ARN of the target group created by the previous command:
TARGET_GROUP_ARN=arn:aws:elasticloadbalancing:eu-west-1:306378194054:targetgroup/tines-app/66559e249e21308d

# Replace this with the ARN of a certificate that you created at step 1
CERTIFICATE_ARN=arn:aws:acm:eu-west-1:306378194054:certificate/85b79526-e45f-4e76-8e3a-1d407142a62a

aws elbv2 create-listener \
  --load-balancer-arn $LOAD_BALANCER_ARN  \
  --protocol HTTPS \
  --port 443 \
  --certificates CertificateArn=$CERTIFICATE_ARN \
  --default-actions '[{"Type": "forward", "TargetGroupArn": "'$TARGET_GROUP_ARN'"}]'

7. Create a .env file 

Make sure to make any necessary changes to the values in the .env file before moving on to step 8.

cat << EOF > tines.env

#############################
# Required: Tenant Configuration #
#############################

# A human friendly identifier for this instance of Tines, e.g. your company name:
TENANT_NAME=company_name
# Company name and stack name (eg. tines_prod). This is used to identify your tenant's telemetry data,
# if you have enabled that feature.
TELEMETRY_ID=company_name_prod

# This will be the first user to be created and get invited to this Tines instance:
SEED_EMAIL=alice@example.com
SEED_FIRST_NAME=Alice
SEED_LAST_NAME=Smith

# The domain that you ultimately want your Tines installation to be accessible at in your browser.
# This should match the SSL certificate from step 1.
DOMAIN=tines.example.com
# This should match the port that you use to access the Tines UI.
# Unless you have chosen a custom port, you should use 443 as typical for HTTPS.
PORT=443

# This should be set to a random 128 character string to ensure security for your installation.
# Changing this value may force users to log in again.
# You can generate a value for this by running: openssl rand -hex 64
APP_SECRET_TOKEN=__SET_YOUR_SECRET_TOKEN__

#############################
# Required: Email Configuration #
#############################
# Outgoing email settings. This must be configured correctly in order for the invite email
# to be sent to the first user.

SMTP_DOMAIN=mail.example.com
SMTP_USER_NAME=AKIAXXXXXXXXXXXXXXXX
SMTP_PASSWORD=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
SMTP_SERVER=email-smtp.eu-west-1.amazonaws.com
SMTP_PORT=587
SMTP_AUTHENTICATION=login
SMTP_ENABLE_STARTTLS_AUTO=true

# This address will be the sender for all emails from this Tines instance:
EMAIL_FROM_ADDRESS=Example Support <tines@example.com>

############################
# Required: Database connections
############################

# Your PostgreSQL database server settings:
# Populate this value using the host name for the database created in step 4:
DATABASE_HOST=tines.xxxxxxxxxxxx.eu-west-1.rds.amazonaws.com
DATABASE_NAME=tines
DATABASE_POOL=20
DATABASE_USERNAME=tines
# Populate this value using the password you set in step 4:
DATABASE_PASSWORD=xxxxxxxxx
DATABASE_PORT=5432

# Populate this value using the endpoint for the Redis cluster created in step 5:
REDIS_URL=redis://tines.xxxxxx.0001.euw1.cache.amazonaws.com:6379/1

########################
# Optional feature configuration #
########################

# Enables a periodic job to update public template date from template-data.tines.com
SYNC_TEMPLATES=true

########################
# Core configuration
#
# These values should not be changed.
########################

# Ensure system logs are included in Docker container logs.
RAILS_LOG_TO_STDOUT=true

# Configure Rails environment. This should always be set to 'production'.
RAILS_ENV=production

# Force all requests to use SSL.
FORCE_SSL=true

# Set the installation's timezone.
TIMEZONE=UTC

# Set worker count
SIDEKIQ_CONCURRENCY=6

EOF

8. Upload the .env file to S3 

Both the tines-app and tines-sidekiq containers need to run with the exact same environment variables. To make this a bit easier, we have them both fetch the same .env file from S3.

# Replace this with your own unique bucket name:
ENV_FILE_S3_BUCKET=tines-test-env

# Replace this with the name of AWS region you're running Tines in:
AWS_REGION=eu-west-1

aws s3api create-bucket \
  --bucket $ENV_FILE_S3_BUCKET \
  --region $AWS_REGION \
  --create-bucket-configuration "LocationConstraint=$AWS_REGION"

aws s3api put-public-access-block \
  --bucket $ENV_FILE_S3_BUCKET \
  --public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

aws s3 cp tines.env s3://$ENV_FILE_S3_BUCKET/tines.env

9. Create the IAM roles for running the containers 

# This only needs to be create once for an AWS account - if you're already using ECS, you can skip this command:
aws iam create-service-linked-role \
  --aws-service-name ecs.amazonaws.com

aws iam create-role \
  --role-name tinesTaskExecutionRole \
  --assume-role-policy-document '{ "Version": "2012-10-17", "Statement": [{ "Sid": "", "Effect": "Allow", "Principal": { "Service": "ecs-tasks.amazonaws.com" }, "Action": "sts:AssumeRole" }]}'

aws iam attach-role-policy \
  --role-name tinesTaskExecutionRole \
  --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

# Replace this with the name of the bucket you created in step 8:
ENV_FILE_S3_BUCKET="tines-test-env"

aws iam put-role-policy \
  --role-name tinesTaskExecutionRole \
  --policy-name TinesEnvAccess \
  --policy-document '{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": ["arn:aws:s3:::'$ENV_FILE_S3_BUCKET'/tines.env"]}, { "Effect": "Allow", "Action": ["s3:GetBucketLocation"], "Resource": ["arn:aws:s3:::'$ENV_FILE_S3_BUCKET'"]}]}'

10. Create the ECS task definitions and cluster 

aws logs create-log-group --log-group-name tines

# Replace this with the ARN of the role you created in step 9:
EXECUTION_ROLE_ARN=arn:aws:iam::306378194054:role/tinesTaskExecutionRole

# Replace this with the address of the registry created in step 2:
REGISTRY=306378194054.dkr.ecr.eu-west-1.amazonaws.com

# Replace this with the name of the image you added to the registry in step 2:
IMAGE=tines-app:de4875d_v4.2.0

# Replace this with the name of the bucket you created in step 8:
ENV_FILE_S3_BUCKET=tines-test-env

# Replace this with the name of AWS region you're running Tines in:
AWS_REGION=eu-west-1

aws ecs register-task-definition \
  --family "tines-app" \
  --memory 2048 \
  --network-mode awsvpc \
  --cpu 1024 \
  --execution-role-arn $EXECUTION_ROLE_ARN \
  --container-definitions '[{"name": "tines-app", "command": ["start-tines-app"], "image": "'$REGISTRY'/'$IMAGE'", "environmentFiles": [{"value": "arn:aws:s3:::'$ENV_FILE_S3_BUCKET'/tines.env", "type": "s3"}], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "tines", "awslogs-region": "'$AWS_REGION'", "awslogs-stream-prefix": "tines" }}, "portMappings": [{"containerPort": 3000}]}]'

aws ecs register-task-definition \
  --family "tines-sidekiq" \
  --memory 2048 \
  --network-mode awsvpc \
  --cpu 1024 \
  --execution-role-arn $EXECUTION_ROLE_ARN \
  --container-definitions '[{"name": "tines-sidekiq", "command": ["start-tines-sidekiq"], "image": "'$REGISTRY'/'$IMAGE'", "environmentFiles": [{"value": "arn:aws:s3:::'$ENV_FILE_S3_BUCKET'/tines.env", "type": "s3"}], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "tines", "awslogs-region": "'$AWS_REGION'", "awslogs-stream-prefix": "tines" }}}]'

aws ecs create-cluster \
  --cluster-name tines

11. Seed the database 

We run a one-off task, with a one-off command to prepare the database. This command will also trigger an invite email to the seed email address you specified in step 7. The link in that email will work once we complete the next step.

# Replace these with the IDs of two subnets from your VPC:
SUBNET_IDS=subnet-606e7706,subnet-bd5bb2f6

# Replace this with the ID of the tines-sidekiq security group created above:
SIDEKIQ_SECURITY_GROUP_ID=sg-01eb5f237d80b8458

aws ecs run-task \
  --cluster tines \
  --task-definition tines-app:1 \
  --launch-type "FARGATE" \
  --network-configuration "awsvpcConfiguration={subnets=[$SUBNET_IDS],securityGroups=[$SIDEKIQ_SECURITY_GROUP_ID],assignPublicIp=ENABLED}" \
  --overrides '{ "containerOverrides": [{ "name": "tines-app", "command": ["prepare-database"]}] }'

12. Start the services 

Once the tines-app and tines-sidekiq services are both up and running, you can accept the invite in the email sent during step 11 to get started:

# Replace these with the IDs of two subnets from your VPC:
SUBNET_IDS=subnet-606e7706,subnet-bd5bb2f6

# Replace this with the ID of the tines-sidekiq security group created above:
SIDEKIQ_SECURITY_GROUP_ID=sg-023c19f4c06195ec7

aws ecs create-service \
  --cluster tines \
  --service-name tines-sidekiq \
  --task-definition tines-sidekiq:1 \
  --desired-count 1 \
  --launch-type "FARGATE" \
  --network-configuration "awsvpcConfiguration={subnets=[$SUBNET_IDS],securityGroups=[$SIDEKIQ_SECURITY_GROUP_ID],assignPublicIp=ENABLED}"

# Replace this with the ID of the tines-app security group created above:
APP_SECURITY_GROUP_ID=sg-01eb5f237d80b8458

# Replace this with the ARN of the target group created in step 6:
TARGET_GROUP_ARN=arn:aws:elasticloadbalancing:eu-west-1:306378194054:targetgroup/tines-app/66559e249e21308d

aws ecs create-service \
  --cluster tines \
  --service-name tines-app \
  --task-definition tines-app:1 \
  --desired-count 1 \
  --launch-type "FARGATE" \
  --network-configuration "awsvpcConfiguration={subnets=[$SUBNET_IDS],securityGroups=[$APP_SECURITY_GROUP_ID],assignPublicIp=ENABLED}" \
  --load-balancers "targetGroupArn=$TARGET_GROUP_ARN,containerName=tines-app,containerPort=3000"

A. Optional - install sql-over-http 

Note

A1. Prepare the sql-over-http Docker image 

Again, to make things a little easier, we copy the image from Docker Hub into an ECR repository:

aws --profile test ecr create-repository --repository-name sql-over-http

# Replace this with the address of the registry output in the previous command:
REGISTRY=306378194054.dkr.ecr.eu-west-1.amazonaws.com

aws ecr get-login-password --region eu-west-1 | \
  docker login --username AWS --password-stdin $REGISTRY

SOH_IMAGE=sql-over-http:latest

docker pull tines/$SOH_IMAGE
docker tag tines/$SOH_IMAGE $REGISTRY/$SOH_IMAGE
docker push $REGISTRY/$SOH_IMAGE

A2. Update the ECS task definitions 

To make it easy to access, we simply deploy the sql-over-http container alongside the existing containers by adding them to the tines-app and tines-sidekiq tasks. (sql-over-http is completely stateless, so running two instances is easiest.)

The command below repeats what we did in step 10 above, but adds the following container definition to both tasks:

{
  "name": "sql-over-http",
  "image": "$REGISTRY/sql-over-http:latest",
  "environment": [
    {
      "name": "PORT",
      "value": "4000"
    }
  ],
  "logConfiguration": {
    "logDriver": "awslogs",
    "options": {
      "awslogs-group": "tines",
      "awslogs-region": "$AWS_REGION",
      "awslogs-stream-prefix": "tines"
    }
  },
  "portMappings": [
    {
      "containerPort": 4000
    }
  ]
}
# Replace this with the ARN of the role you created in step 9:
EXECUTION_ROLE_ARN=arn:aws:iam::306378194054:role/tinesTaskExecutionRole

# Replace this with the address of the registry as established in step 2:
REGISTRY=306378194054.dkr.ecr.eu-west-1.amazonaws.com

# Replace this with the name of the image you added to the registry in step 2:
IMAGE=tines-app:de4875d_v4.2.0

# Replace this with the name of the image you added to the registry in step A1:
SOH_IMAGE=sql-over-http:latest

# Replace this with the name of the bucket you created in step 8:
ENV_FILE_S3_BUCKET=tines-test-env

# Replace this with the name of AWS region you're running Tines in:
AWS_REGION=eu-west-1

aws ecs register-task-definition \
  --family "tines-app" \
  --memory 2048 \
  --network-mode awsvpc \
  --cpu 1024 \
  --execution-role-arn $EXECUTION_ROLE_ARN \
  --container-definitions '[{"name": "tines-app", "command": ["start-tines-app"], "image": "'$REGISTRY'/'$IMAGE'", "environmentFiles": [{"value": "arn:aws:s3:::'$ENV_FILE_S3_BUCKET'/tines.env", "type": "s3"}], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "tines", "awslogs-region": "'$AWS_REGION'", "awslogs-stream-prefix": "tines" }}, "portMappings": [{"containerPort": 3000}]},{"name": "sql-over-http", "image": "'$REGISTRY'/'$SOH_IMAGE'", "environment": [{"name": "PORT", "value": "4000"}], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "tines", "awslogs-region": "'$AWS_REGION'", "awslogs-stream-prefix": "tines" }}, "portMappings": [{"containerPort": 4000}]}]'

aws ecs register-task-definition \
  --family "tines-sidekiq" \
  --memory 2048 \
  --network-mode awsvpc \
  --cpu 1024 \
  --execution-role-arn $EXECUTION_ROLE_ARN \
  --container-definitions '[{"name": "tines-sidekiq", "command": ["start-tines-sidekiq"], "image": "'$REGISTRY'/'$IMAGE'", "environmentFiles": [{"value": "arn:aws:s3:::'$ENV_FILE_S3_BUCKET'/tines.env", "type": "s3"}], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "tines", "awslogs-region": "'$AWS_REGION'", "awslogs-stream-prefix": "tines" }}},{"name": "sql-over-http", "image": "'$REGISTRY'/'$SOH_IMAGE'", "environment": [{"name": "PORT", "value": "4000"}], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "tines", "awslogs-region": "'$AWS_REGION'", "awslogs-stream-prefix": "tines" }}, "portMappings": [{"containerPort": 4000}]}]'

A3. Restart the services 

If you delete the tines-app and tines-sidekiq services via the UI, you can then re-run the commands from step 12.

The one thing you'll need to change:

  --task-definition tines-app:1
  --task-definition tines-sidekiq:1

...will need to be incremented to reflect the latest versions of the task definitions, following the update that happened in step A2

  --task-definition tines-app:2
  --task-definition tines-sidekiq:2

A4. Access sql-over-http from within Tines 

From within Tines, sql-over-http will be accessible at http://localhost:4000/.

B. Optional - install Tines Tunnel 

Note

B1. Prepare the tines-tunnel Docker image 

Again, to make things a little easier, we copy the image from Docker Hub into an ECR repository:

aws ecr create-repository --repository-name tines-tunnel

# Replace this with the address of the registry output in the previous command:
REGISTRY=306378194054.dkr.ecr.eu-west-1.amazonaws.com

aws ecr get-login-password --region eu-west-1 | \
  docker login --username AWS --password-stdin $REGISTRY

TUNNEL_IMAGE=tines-tunnel:latest

docker pull tines/$TUNNEL_IMAGE
docker tag tines/$TUNNEL_IMAGE $REGISTRY/$TUNNEL_IMAGE
docker push $REGISTRY/$TUNNEL_IMAGE

B2. Create and upload the .env file to S3 

# Replace this with your own unique bucket name:
ENV_FILE_S3_BUCKET=tines-tunnel-env

# Replace this with the name of AWS region you're running Tines in:
AWS_REGION=eu-west-1

aws s3api create-bucket \
  --bucket $ENV_FILE_S3_BUCKET \
  --region $AWS_REGION \
  --create-bucket-configuration "LocationConstraint=$AWS_REGION"

aws s3api put-public-access-block \
  --bucket $ENV_FILE_S3_BUCKET \
  --public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

cat << EOF > tines-tunnel.env

# Copy this variable from https://<your-tenant>.tines.com/admin/tunnel
# Make sure to remove any quote characters from the value
TINES_TUNNEL_SECRET=...

EOF

aws s3 cp tines-tunnel.env s3://$ENV_FILE_S3_BUCKET/tines-tunnel.env

B3. Create the IAM roles for running the containers 

# This only needs to be create once for an AWS account - if you're already using ECS, you can skip this command:
aws iam create-service-linked-role \
  --aws-service-name ecs.amazonaws.com

aws iam create-role \
  --role-name tinesTunnelTaskExecutionRole \
  --assume-role-policy-document '{ "Version": "2012-10-17", "Statement": [{ "Sid": "", "Effect": "Allow", "Principal": { "Service": "ecs-tasks.amazonaws.com" }, "Action": "sts:AssumeRole" }]}'

aws iam attach-role-policy \
  --role-name tinesTunnelTaskExecutionRole \
  --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

# Replace this with the name of the bucket you created in step B2:
ENV_FILE_S3_BUCKET="tines-tunnel-env"

aws iam put-role-policy \
  --role-name tinesTunnelTaskExecutionRole \
  --policy-name TinesEnvAccess \
  --policy-document '{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": ["arn:aws:s3:::'$ENV_FILE_S3_BUCKET'/tines-tunnel.env"]}, { "Effect": "Allow", "Action": ["s3:GetBucketLocation"], "Resource": ["arn:aws:s3:::'$ENV_FILE_S3_BUCKET'"]}]}'

B4. Prepare a security group 

aws ec2 create-security-group \
  --group-name tines-tunnel \
  --description "tines-tunnel container security group for the Tines Tunnel application"

B5. Create the ECS task definitions and cluster 

aws logs create-log-group --log-group-name tines-tunnel

# Replace this with the ARN of the role you created in step B3:
EXECUTION_ROLE_ARN=arn:aws:iam::306378194054:role/tinesTunnelTaskExecutionRole

# Replace this with the address of the registry created in step B1:
REGISTRY=306378194054.dkr.ecr.eu-west-1.amazonaws.com

# Replace this with the name of the image you added to the registry in step B1:
IMAGE=tines-tunnel:latest

# Replace this with the name of the bucket you created in step B2:
ENV_FILE_S3_BUCKET=tines-tunnel-env

# Replace this with the name of AWS region you're running Tines in:
AWS_REGION=eu-west-1

aws ecs register-task-definition \
  --family "tines-tunnel" \
  --memory 2048 \
  --network-mode awsvpc \
  --cpu 1024 \
  --execution-role-arn $EXECUTION_ROLE_ARN \
  --container-definitions '[{"name": "tines-tunnel", "image": "'$REGISTRY'/'$IMAGE'", "environmentFiles": [{"value": "arn:aws:s3:::'$ENV_FILE_S3_BUCKET'/tines-tunnel.env", "type": "s3"}], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "tines-tunnel", "awslogs-region": "'$AWS_REGION'", "awslogs-stream-prefix": "tines" }}}]'

aws ecs create-cluster --cluster-name tines-tunnel

B6. Start the service 

# Replace these with the IDs of two subnets from your VPC:
SUBNET_IDS=subnet-606e7706,subnet-bd5bb2f6

# Replace this with the ID of the tines-tunnel security group created above:
SECURITY_GROUP_ID=sg-01eb5f237d80b8458

aws ecs create-service \
  --cluster tines-tunnel \
  --service-name tines-tunnel \
  --task-definition tines-tunnel:1 \
  --desired-count 1 \
  --launch-type "FARGATE" \
  --network-configuration "awsvpcConfiguration={subnets=[$SUBNET_IDS],securityGroups=[$SECURITY_GROUP_ID],assignPublicIp=ENABLED}"