Skip to content

Instantly share code, notes, and snippets.

@jonah-williams
Last active May 29, 2019 14:53
Show Gist options
  • Save jonah-williams/5979761 to your computer and use it in GitHub Desktop.
Save jonah-williams/5979761 to your computer and use it in GitHub Desktop.
Automating deployments to Heroku from CircleCI
test:
override:
- bundle exec rspec spec
deployment:
acceptance:
branch: master
commands:
- ./script/heroku_deploy.sh <ACCEPTANCE_HEROKU_APP>:
timeout: 300
staging:
branch: staging
commands:
- ./script/heroku_deploy.sh <STAGING_HEROKU_APP>:
timeout: 300
production:
branch: production
commands:
- ./script/heroku_deploy.sh <PRODUCTION_HEROKU_APP>:
timeout: 300
#!/bin/sh -e
APP_NAME=$1
git remote add heroku git@heroku.com:$APP_NAME.git
git fetch heroku
MIGRATION_CHANGES=$(git diff HEAD heroku/master --name-only -- db | wc -l)
echo "$MIGRATION_CHANGES db changes."
PREV_WORKERS=$(heroku ps --app $APP_NAME | grep "^worker." | wc -l | tr -d ' ')
# migrations require downtime so enter maintenance mode
if test $MIGRATION_CHANGES -gt 0; then
heroku maintenance:on --app $APP_NAME
# Make sure workers are not running during a migration
heroku scale worker=0 --app $APP_NAME
fi
# deploy code changes (and implicitly restart the app and any running workers)
git push heroku $CIRCLE_SHA1:refs/heads/master
# run database migrations if needed and restart background workers once finished
if test $MIGRATION_CHANGES -gt 0; then
heroku run rake db:migrate db:seed --app $APP_NAME
heroku scale worker=$PREV_WORKERS --app $APP_NAME
heroku restart --app $APP_NAME
fi
heroku run rake cache:flush --app $APP_NAME
heroku maintenance:off --app $APP_NAME
#!/bin/sh -e
APP_NAME=$1
heroku maintenance:on --app $APP_NAME
git push heroku $CIRCLE_SHA1:refs/heads/master
heroku run rake db:migrate db:seed --app $APP_NAME
heroku restart --app $APP_NAME
heroku maintenance:off --app $APP_NAME
#!/bin/sh -e
APP_NAME=$1
PIPELINE_APP_NAME=$APP_NAME-slug
HEROKU_APP_REMOTE=git@heroku.com:$APP_NAME.git
HEROKU_PIPELINE_REMOTE=git@heroku.com:$PIPELINE_APP_NAME.git
WORKERS_COUNT=$(heroku ps --app $APP_NAME | grep "^worker." | wc -l | tr -d ' ')
# configure pipeline deploy https://devcenter.heroku.com/articles/labs-pipelines
heroku plugins:install git://github.com/heroku/heroku-pipeline.git
if test $(heroku apps | grep $PIPELINE_APP_NAME -c) -gt 0; then
echo "Using existing pipeline $PIPELINE_APP_NAME"
git remote add pipeline $HEROKU_PIPELINE_REMOTE
DIFF_REMOTE=pipeline
else
heroku apps:create $PIPELINE_APP_NAME --remote pipeline
heroku labs:enable pipelines --app $PIPELINE_APP_NAME
heroku pipeline:add $APP_NAME --app $PIPELINE_APP_NAME
heroku pipeline --app $PIPELINE_APP_NAME
git remote add heroku $HEROKU_APP_REMOTE
DIFF_REMOTE=heroku
fi
# configure app and check for db changes
git fetch $DIFF_REMOTE
MIGRATION_CHANGES=$(git diff $CIRCLE_SHA1 $DIFF_REMOTE/master --name-only -- db | wc -l)
echo "$MIGRATION_CHANGES db changes since last deploy."
# use pipeline to prepare a slug to deploy
git push pipeline $CIRCLE_SHA1:refs/heads/master
DEPLOY_START=`date +%s`
# migrations require downtime so enter maintenance mode
if test $MIGRATION_CHANGES -gt 0; then
heroku maintenance:on --app $APP_NAME
# Make sure workers are not running during a migration
heroku scale worker=0 --app $APP_NAME
fi
# Run our migrations in a subprocess so that failed db migrations
# don't immediately abort our deploy script.
(
# promote the compiled slug
heroku pipeline:promote --app $PIPELINE_APP_NAME
# run database migrations if needed and restart background workers once finished
if test $MIGRATION_CHANGES -gt 0; then
heroku run rake db:migrate --app $APP_NAME
heroku run rake db:seed --app $APP_NAME
heroku restart --app $APP_NAME
fi
)
MIGRATION_EXIT=$? # store the exit code
# Always scale our workers back up, regardless of whether the migration failed
if test $MIGRATION_CHANGES -gt 0; then
heroku scale worker=$WORKERS_COUNT --app $APP_NAME
fi
# If the migration failed, now we can exit with that exit code
if test $MIGRATION_EXIT -ne 0; then
exit $MIGRATION_EXIT
fi
heroku run rake cache:flush --app $APP_NAME
heroku maintenance:off --app $APP_NAME
DEPLOY_END=`date +%s`
ELAPSED=$(( $DEPLOY_END - $DEPLOY_START ))
echo "Deploy completed!"
if test $MIGRATION_CHANGES -gt 0; then
echo " $ELAPSED seconds in maintenance mode."
else
echo " $ELAPSED seconds, without maintenance mode."
fi
exit 0
#!/bin/sh -e
APP_NAME=$1
PIPELINE_APP_NAME=$APP_NAME-slug
HEROKU_APP_REMOTE=git@heroku.com:$APP_NAME.git
HEROKU_PIPELINE_REMOTE=git@heroku.com:$PIPELINE_APP_NAME.git
WORKERS_COUNT=$(heroku ps --app $APP_NAME | grep "^worker." | wc -l | tr -d ' ')
# configure pipeline deploy https://devcenter.heroku.com/articles/labs-pipelines
if test $(heroku apps | grep $PIPELINE_APP_NAME -c) -gt 0; then
echo "Using existing pipeline $PIPELINE_APP_NAME"
git remote add pipeline $HEROKU_PIPELINE_REMOTE
else
heroku apps:create $PIPELINE_APP_NAME --remote pipeline
heroku labs:enable pipelines --app $PIPELINE_APP_NAME
fi
heroku plugins:install git://github.com/heroku/heroku-pipeline.git
heroku pipeline:add $APP_NAME --app $PIPELINE_APP_NAME
heroku pipeline --app $PIPELINE_APP_NAME
# configure app and check for db changes
git remote add heroku $HEROKU_APP_REMOTE
git fetch heroku
MIGRATION_CHANGES=$(git diff $CIRCLE_SHA1 heroku/master --name-only -- db | wc -l)
echo "$MIGRATION_CHANGES db changes since last deploy."
# use pipeline to prepare a slug to deploy
git push pipeline $CIRCLE_SHA1:refs/heads/master
DEPLOY_START=`date +%s`
# migrations require downtime so enter maintenance mode
if test $MIGRATION_CHANGES -gt 0; then
heroku maintenance:on --app $APP_NAME
# Make sure workers are not running during a migration
heroku scale worker=0 --app $APP_NAME
fi
# promote the compiled slug
heroku pipeline:promote --app $PIPELINE_APP_NAME
# run database migrations if needed and restart background workers once finished
if test $MIGRATION_CHANGES -gt 0; then
heroku run rake db:migrate db:seed --app $APP_NAME
heroku restart --app $APP_NAME
heroku scale worker=$PREV_WORKERS --app $APP_NAME
fi
heroku run rake cache:flush --app $APP_NAME
heroku maintenance:off --app $APP_NAME
DEPLOY_END=`date +%s`
ELAPSED=$(( $DEPLOY_END - $DEPLOY_START ))
# HACK!
# Pipelines do not update the git repository on downstream apps.
#
# In order to be able to diff for db changes against the app overwrite the pipeline
# deploy with a git push of the same commit.
#
# Since this we already ran any required migrations this can always be a
# zero-downtime deploy we don't need to enable maintenance mode and are no longer
# sensetive to long asset precompilation times or other delays.
#
# We could remove this if we want to maintain a persistent pipeline app and always
# diff against that app to determine if db changes exist. Alternately we might
# be able to parse `heroku pipeline:diff` output for commit hashes and check for
# db changes in each commit.
git push heroku $CIRCLE_SHA1:refs/heads/master -f
# remove the pipeline app
heroku apps:destroy --app $PIPELINE_APP_NAME --confirm $PIPELINE_APP_NAME
echo "Deploy completed!"
if test $MIGRATION_CHANGES -gt 0; then
echo " $ELAPSED seconds in maintenance mode."
else
echo " $ELAPSED seconds, without maintenance mode."
fi
#!/bin/sh -e
APP_NAME=$1
git remote add heroku git@heroku.com:$APP_NAME.git
git fetch heroku
MIGRATION_CHANGES=$(git diff HEAD heroku/master --name-only -- db | wc -l)
echo "$MIGRATION_CHANGES db changes since last deploy."
rake assets:precompile
git config user.name "CircleCi"
git config user.email "ci@example.com"
git add public/assets
git commit -m "Precompile assets"
PREV_WORKERS=$(heroku ps --app $APP_NAME | grep "^worker." | wc -l | tr -d ' ')
# migrations require downtime so enter maintenance mode
if test $MIGRATION_CHANGES -gt 0; then
heroku maintenance:on --app $APP_NAME
# Make sure workers are not running during a migration
heroku scale worker=0 --app $APP_NAME
fi
# deploy code changes (and implicitly restart the app and any running workers)
git push heroku HEAD:refs/heads/master -f
# run database migrations if needed and restart background workers once finished
if test $MIGRATION_CHANGES -gt 0; then
heroku run rake db:migrate db:seed --app $APP_NAME
heroku restart --app $APP_NAME
heroku scale worker=$PREV_WORKERS --app $APP_NAME
fi
heroku run rake cache:flush --app $APP_NAME
heroku maintenance:off --app $APP_NAME
@christiannelson
Copy link

@jonah-carbonfive what does your circle.yml file look like?

@jonah-williams
Copy link
Author

Added git fetch heroku to make sure the CI box can diff against the latest git deploy to Heroku.
Added example of circle.yml.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment