Just some notes on the process. This will show how to deploy both a Staging build and then Production.


Staging is done by TravisCI after all tests pass

Here is the gist of it the deploy step calls to a bash file.

 skip_cleanup: true
 provider: script
 script: bash deploy/travis_deploy.sh
   branch: mainline


#!/usr/bin/env bash

# Bail out on first error
set -e

## Get the directory of the build script
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

## Get the current git commit sha
HASH=$(git rev-parse HEAD)

## Get any secret files
aws s3 cp s3://foo/environments/$STACK_ENV_FILE $DIR/app/packaged/.env

##we only want non-dev vendors
composer config -g github-oauth.github.com $GITHUB_TOKEN && composer install --no-dev

echo "Region $STACK_AWS_REGION is the target region"
eval $(aws ecr get-login --no-include-email --region $STACK_AWS_REGION)
echo "Tagging images $STACK_APP_NAME"
docker build --pull -t $STACK_APP_NAME .
docker tag $STACK_APP_NAME:latest 1111111111.dkr.ecr.$STACK_AWS_REGION.amazonaws.com/$STACK_APP_NAME:latest
echo "Pushing up image $STACK_APP_NAME:latest"
docker push 1111111111.dkr.ecr.us-east-1.amazonaws.com/$STACK_APP_NAME:latest

## Now Run again for Production WILL COME BACK TO THIS IN A MOMENT
## if production set???
    echo "Running Production build"
    aws s3 cp s3://foo/environments/$STACK_ENV_FILE_PRODUCTION $DIR/app/packaged/.env
    echo "Building Production Image"
    docker build --pull -t $STACK_APP_NAME .
    docker tag $STACK_APP_NAME:latest 1111111111.dkr.ecr.us-east-1.amazonaws.com/$STACK_APP_NAME:production_$HASH
    echo "Pushing up production image using has production_$HASH"
    docker push 1111111111.dkr.ecr.us-east-1.amazonaws.com/$STACK_APP_NAME:production_$HASH

So Staging will build and push right to the AWS ECR which means Fargate by default will get the Latest tagged image since the TaskDefinition says so. So staging is done. Next task will run this one.


This we want to happen by choice not by Travis. So you can see the step in Travis STACK_ENV_FILE_PRODUCTION that looks for an environment variable and if true it will push the same working image but with it’s own secrets to ECR but with the tag production_GIT_HASH

Then we ready we have a UI to push it BUT really it is just CloudFormation that updates the TaskDefinition using the build in Params to make it reference this HASH. This can be done pretty easily with Python, PHP etc and the AWS SDK that allows you to update CloudFormation and the Parameter that then fills in the TaskDefinition Field and updates it, from there the next time the Production Fargate runs it runs that latest version.

Example Task Definition:

    "TaskDefinition": {
      "Type": "AWS::ECS::TaskDefinition",
      "Properties": {
        "ExecutionRoleArn": "",
        "Memory": 250,
        "NetworkMode": "bridge",
        "TaskRoleArn": "arn:aws:iam::364215618558:role/foo",
        "ContainerDefinitions": [{
          "Name": {
            "Fn::Sub": "${AppName}-${AppEnv}"
          "Image": {
            "Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${AppName}:${ProductionTag}"
          "PortMappings": [{
              "ContainerPort": 80,
              "HostPort": {
                "Ref": "AppPort"
              "ContainerPort": 443,
              "HostPort": {
                "Ref": "AppPortSSL"
          "Memory": 250,
          "MountPoints": [{
            "SourceVolume": "shared",
            "ContainerPath": "/opt/shared"
        "Volumes": [{
          "Name": "shared",
          "Host": {
            "SourcePath": "/opt/shared"
      "DependsOn": [

So when I update this I can just update the ProductionTag and this will take effect.