├── .dockerignore ├── LICENSE ├── README.md ├── bin ├── deploy ├── deploy_to_production ├── deploy_to_staging └── docker_entrypoint └── docker ├── Dockerfile ├── docker-compose.env └── docker-compose.yml /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | log 3 | tmp 4 | vendor 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Atomic Object 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ECS Deployment 2 | 3 | Sample ECS Deployment script with example Dockerfile, and repository setup. 4 | 5 | Execute with: 6 | 7 | - `./bin/deploy -e environmentname -b branchname` 8 | -------------------------------------------------------------------------------- /bin/deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # DESCRIPTION: ECS Deployment Script 4 | # MAINTAINER: Justin Kulesza = 4.4.12), python (~> 2.7.13), awscli (~> 1.11.91), docker-ce (>= 17.03.1) 6 | # FROM: https://github.com/atomicobject/ecs-deployment 7 | # 8 | 9 | set -e 10 | 11 | # BEGIN CUSTOMIZATIONS # 12 | ECS_REGION='us-west-2' 13 | ECS_CLUSTER_NAME='name-of-ecs-cluster' 14 | ECS_SERVICE_NAME='NameOfService' 15 | ECS_TASK_DEFINITION_NAME='NameOfTaskDefinition' 16 | ECR_NAME='name-of-ecr' 17 | ECR_URI='account-number.dkr.ecr.us-west-2.amazonaws.com' 18 | VERSION=$(date +%s) 19 | AWSCLI_VER_TAR=1.11.91 20 | # END CUSTOMIZATIONS # 21 | 22 | # BEGIN OTHER VAR DEFINITIONS # 23 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 24 | ORIGINAL_BRANCH="$(git rev-parse --abbrev-ref HEAD)" 25 | ENVIRONMENT="" 26 | BRANCH="" 27 | AWSCLI_VER=$(aws --version 2>&1 | cut -d ' ' -f 1 | cut -d '/' -f 2) 28 | # END OTHER VAR DEFINITIONS # 29 | 30 | if [[ ${AWSCLI_VER} < ${AWSCLI_VER_TAR} ]] 31 | then echo "ERROR: Please upgrade your AWS CLI to version ${AWSCLI_VER_TAR} or later!" 32 | exit 1 33 | fi 34 | 35 | usage() { 36 | echo "Usage: $0 -e [-b ]" 37 | echo " must be either 'staging' or 'production'" 38 | echo " must be a valid ref. If no branch is provided, you will be prompted for one." 39 | exit 1 40 | } 41 | 42 | while getopts ":e:b:h" o; do 43 | case "${o}" in 44 | e) 45 | ENVIRONMENT=${OPTARG} 46 | (("${ENVIRONMENT}" == "staging" || "${ENVIRONMENT}" == "production")) || usage 47 | ;; 48 | b) 49 | BRANCH=${OPTARG} 50 | git rev-parse --abbrev-ref "${BRANCH}" || usage 51 | ;; 52 | *) 53 | usage 54 | ;; 55 | esac 56 | done 57 | shift $((OPTIND-1)) 58 | 59 | if [[ -z "${ENVIRONMENT}" ]] ; then 60 | usage 61 | fi 62 | 63 | if [[ -z "${BRANCH}" ]] ; then 64 | echo -n "Which branch to deploy from [$(git rev-parse --abbrev-ref HEAD)] ? " 65 | read -r line 66 | if [[ -z "${line}" ]]; then 67 | BRANCH="$(git rev-parse --abbrev-ref HEAD)" 68 | else 69 | git rev-parse --abbrev-ref "${line}" || exit 1 70 | BRANCH="${line}" 71 | fi 72 | fi 73 | 74 | echo "You are deploying ${BRANCH} to ${ENVIRONMENT}." 75 | 76 | # Git operations 77 | ( 78 | cd "${DIR}/.." 79 | 80 | # Fetch latest from git origin 81 | git fetch --all 82 | 83 | # Checkout the git branch 84 | git checkout "${BRANCH}" 85 | ) 86 | 87 | 88 | # Docker operations 89 | ( 90 | cd "${DIR}/.." 91 | 92 | # Build the Docker image (to do asset and template compilation, etc.) 93 | docker build --pull -t "${ECR_NAME}:latest" -f ./docker/Dockerfile . 94 | 95 | # Tag the new Docker image to the remote repo (by date) 96 | docker tag "${ECR_NAME}:latest" "${ECR_URI}/${ECR_NAME}:${ENVIRONMENT}-${VERSION}" 97 | 98 | # Tag the new Docker staging image to the remote repo (by 'latest-${ENVIRONMENT}') 99 | docker tag "${ECR_NAME}:latest" "${ECR_URI}/${ECR_NAME}:latest-${ENVIRONMENT}" 100 | 101 | # Login to ECR 102 | $(aws ecr get-login --region "${ECS_REGION}") 103 | 104 | # Push to the remote repo (by date) 105 | docker push "${ECR_URI}/${ECR_NAME}:${ENVIRONMENT}-${VERSION}" 106 | 107 | # Push to the remote repo (by 'latest-staging') 108 | docker push "${ECR_URI}/${ECR_NAME}:latest-${ENVIRONMENT}" 109 | ) 110 | 111 | # ECS operations 112 | ( 113 | cd "${DIR}/.." 114 | 115 | # Store revision 116 | REVISION=$(git rev-parse "${BRANCH}") 117 | 118 | # Get previous task definition from ECS 119 | PREVIOUS_TASK_DEF=$(aws ecs describe-task-definition --region "${ECS_REGION}" --task-definition "${ECS_TASK_DEFINITION_NAME}-${ENVIRONMENT}") 120 | 121 | # Create the new ECS container definition from the last task definition 122 | NEW_CONTAINER_DEF=$(echo "${PREVIOUS_TASK_DEF}" | python <(cat <<-EOF 123 | import sys, json 124 | definition = json.load(sys.stdin)['taskDefinition']['containerDefinitions'] 125 | definition[0]['environment'].extend([ 126 | { 127 | 'name' : 'REVISION', 128 | 'value' : '${REVISION}' 129 | }, 130 | { 131 | 'name' : 'RELEASE_VERSION', 132 | 'value' : '${VERSION}' 133 | }, 134 | { 135 | 'name' : 'ENVIRONMENT', 136 | 'value' : '${ENVIRONMENT}' 137 | }]) 138 | definition[0]['image'] = '${ECR_URI}/${ECR_NAME}:${ENVIRONMENT}-${VERSION}' 139 | print json.dumps(definition) 140 | EOF 141 | )) 142 | 143 | # Create the new task definition 144 | aws ecs register-task-definition --region "${ECS_REGION}" --family "${ECS_TASK_DEFINITION_NAME}-${ENVIRONMENT}" --container-definitions "${NEW_CONTAINER_DEF}" 145 | 146 | # Update the service to use the new task defintion 147 | aws ecs update-service --region "${ECS_REGION}" --cluster "${ECS_CLUSTER_NAME}" --service "${ECS_SERVICE_NAME}-${ENVIRONMENT}" --task-definition "${ECS_TASK_DEFINITION_NAME}-${ENVIRONMENT}" 148 | ) 149 | 150 | # Return to original branch 151 | ( 152 | cd "${DIR}/.." 153 | 154 | # Checkout the original branch 155 | git checkout "${ORIGINAL_BRANCH}" 156 | ) 157 | -------------------------------------------------------------------------------- /bin/deploy_to_production: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | 7 | ( 8 | cd "${DIR}/.." 9 | ./bin/deploy -e production -b master 10 | ) 11 | -------------------------------------------------------------------------------- /bin/deploy_to_staging: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | 7 | ( 8 | cd "${DIR}/.." 9 | ./bin/deploy -e staging -b master 10 | ) 11 | -------------------------------------------------------------------------------- /bin/docker_entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # Apache gets grumpy about PID files pre-existing 5 | rm -f /usr/local/apache2/logs/httpd.pid 6 | 7 | chown -R apache: /var/www/current 8 | exec httpd -DFOREGROUND 9 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:edge 2 | 3 | LABEL maintainer "Justin Kulesza " 4 | 5 | RUN apk update && \ 6 | apk add \ 7 | apache2 && \ 8 | rm -rf /var/cache/apk/* 9 | 10 | RUN mkdir -p /var/www/localhost/htdocs 11 | 12 | ADD . /var/www/localhost/htdocs 13 | 14 | WORKDIR /var/www/localhost/htdocs 15 | 16 | ENTRYPOINT ./bin/docker_entrypoint 17 | -------------------------------------------------------------------------------- /docker/docker-compose.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgres://user:password@host:5432/dbname 2 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | server: 4 | build: 5 | context: '..' 6 | dockerfile: './docker/Dockerfile' 7 | entrypoint: './bin/docker_entrypoint' 8 | working_dir: '/var/www/localhost/htdocs' 9 | env_file: 10 | - 'docker-compose.env' 11 | ports: 12 | - '3000:3000' 13 | --------------------------------------------------------------------------------