Skip to content

Instantly share code, notes, and snippets.

@rfunduk
Last active September 21, 2020 23:49
Show Gist options
  • Save rfunduk/f35587db83d8d32b02c0404eb8ffae8c to your computer and use it in GitHub Desktop.
Save rfunduk/f35587db83d8d32b02c0404eb8ffae8c to your computer and use it in GitHub Desktop.
Manual Elixir red/green deployments
[Unit]
Description=AppName Phoenix
After=network.target
[Install]
WantedBy=multi-user.target
[Service]
User=ubuntu
Group=www-data
Environment=PORT=ACTIVE_PORT
EnvironmentFile=/home/ubuntu/APP_NAME.ACTIVE.env
#ExecStartPre=/usr/bin/install -m 777 /dev/null /tmp/APP_NAME.ACTIVE.sock
ExecStart=/home/ubuntu/APP_NAME.deployment/APP_NAME.ACTIVE/bin/APP_NAME start
ExecStop=/home/ubuntu/APP_NAME.deployment/APP_NAME.ACTIVE/bin/APP_NAME stop
ExecReload=/home/ubuntu/APP_NAME.deployment/APP_NAME.ACTIVE/bin/APP_NAME restart
SyslogIdentifier=APP_NAME
Restart=always
RestartSec=5
StartLimitBurst=3
StartLimitInterval=10
#!/usr/bin/env bash
set -e
BIN_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
cd $BIN_DIR/..
export MIX_ENV=prod
source .env.$MIX_ENV
rm -rf /tmp/$APP_NAME-deploy
mkdir -p /tmp/$APP_NAME-deploy
git archive master | tar -x -C /tmp/$APP_NAME-deploy
git rev-parse --short HEAD > /tmp/$APP_NAME-deploy/REVISION
cd /tmp/$APP_NAME-deploy
MIX_QUIET=1 mix local.hex --force && mix local.rebar --force
MIX_QUIET=1 mix deps.get --only prod
mix compile
(cd ./assets && yarn install --frozen-lockfile)
(cd ./assets && yarn run deploy)
mix phx.digest
MIX_QUIET=1 mix release --overwrite
#!/usr/bin/env bash
set -e
if [ ! -z "$(git status --porcelain)" ]; then
echo "Working directory not clean."
exit 1
fi
BIN_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
cd $BIN_DIR/..
perl -lpe 'BEGIN { sub inc { my ($num) = @_; ++$num } } s/(?<=version: \"0.0.)(\d+)/(inc($1))/eg' -i mix.exs
git add mix.exs
git commit --no-verify -m "Bump version to $(MIX_QUIET=1 mix version)"
#!/usr/bin/env bash
set -e
BIN_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
cd $BIN_DIR/..
APP_NAME=myapp
APP_NAMESPACE=Myapp
TARGET_HOST=$1
if [ ! -z "$(git status --porcelain)" ]; then
echo "Working directory not clean."
exit 1
fi
./bin/bump.sh && git push
if [ -z $TARGET_HOST ]; then
echo "Specify target host as argument."
exit 1
fi
APP_VERSION=$(MIX_QUIET=1 mix version)
PREFIX="~/$APP_NAME.deployment/$APP_NAME-$APP_VERSION"
echo -n "Determine current active release: "
OLD_ACTIVE=$(ssh $TARGET_HOST "cat $APP_NAME.active || echo -n 'b'")
echo $OLD_ACTIVE
case $OLD_ACTIVE in
"a") NEW_ACTIVE="b";;
"b") NEW_ACTIVE="a";;
esac
declare -A PORTS=( ["a"]="3001" ["b"]="3002" )
TARGET="~/$APP_NAME.deployment/$APP_NAME.$NEW_ACTIVE"
echo "Deploying to new active: $NEW_ACTIVE"
echo
echo "*******************************************"
echo "************* BUILD RELEASE ***************"
echo "*******************************************"
APP_NAME=$APP_NAME ./bin/build.sh
echo -e "Done.\n\n"
echo "*******************************************"
echo "************* UPLOAD RELEASE **************"
echo "*******************************************"
ssh $TARGET_HOST "mkdir -p ~/$APP_NAME.deployment"
rsync -ra --quiet /tmp/$APP_NAME-deploy/_build/prod/rel/$APP_NAME \
$TARGET_HOST:$APP_NAME.deployment/$APP_NAME-$APP_VERSION
# maybe like this instead?
# (cd /tmp/$APP_NAME-deploy/_build/prod/rel/ && tar czf - $APP_NAME) |
# ssh $TARGET_HOST 'cd $APP_NAME && tar xzf -'
echo -e "Done.\n\n"
echo "*******************************************"
echo "************* UPDATE CONFIG ***************"
echo "*******************************************"
for v in a b; do
cat config/deploy/nginx.conf |
sed "s/ACTIVE_PORT/${PORTS["$v"]}/g" - |
sed "s/ACTIVE/$v/g" - |
ssh $TARGET_HOST "cat - | sudo tee /etc/nginx/sites-available/$APP_NAME.$v.conf" > /dev/null
done
for v in a b; do
cat config/deploy/app.service |
sed "s/ACTIVE_PORT/${PORTS["$v"]}/g" - |
sed "s/ACTIVE/$v/g" - |
ssh $TARGET_HOST "cat - | sudo tee /etc/systemd/system/$APP_NAME.$v.service" > /dev/null
done
ssh $TARGET_HOST "sudo systemctl daemon-reload"
echo "Ensure old inactive stopped."
ssh $TARGET_HOST "sudo systemctl stop $APP_NAME.$NEW_ACTIVE || exit 0"
echo -e "Done.\n\n"
echo "*******************************************"
echo "************* UPGRADE RELEASE *************"
echo "*******************************************"
echo "Upload production env..."
scp .env.prod $TARGET_HOST:$APP_NAME.$NEW_ACTIVE.env
echo "Establish new active release..."
ssh $TARGET_HOST "rm -f $TARGET && ln -sf $PREFIX/$APP_NAME $TARGET"
echo "Symlink static..."
ssh $TARGET_HOST "ln -sf $TARGET/lib/$APP_NAME-$APP_VERSION/priv/static $TARGET/static"
echo "Migrate database..."
ssh $TARGET_HOST "set -o allexport; source ~/$APP_NAME.$NEW_ACTIVE.env; set +o allexport && PORT=0 $TARGET/bin/$APP_NAME eval '$APP_NAMESPACE.Release.migrate'"
echo "Swap active to $NEW_ACTIVE..."
# start new release
ssh $TARGET_HOST "sudo systemctl start $APP_NAME.$NEW_ACTIVE"
# wait until new release is booted and accepting connections
echo "Waiting for release to boot..."
ssh $TARGET_HOST "while true; do curl -s localhost:${PORTS["$NEW_ACTIVE"]} > /dev/null && { echo -e \"\nRelease is up.\"; break; } || { echo -n '.'; sleep 0.5; } done"
# swap nginx configs to new active
ssh $TARGET_HOST "sudo rm -f /etc/nginx/sites-enabled/$APP_NAME.conf && sudo ln -sf /etc/nginx/sites-available/$APP_NAME.$NEW_ACTIVE.conf /etc/nginx/sites-enabled/$APP_NAME.conf"
# reload nginx to start sending traffic to new active
ssh $TARGET_HOST "sudo systemctl reload nginx"
# update new ACTIVE
ssh $TARGET_HOST "echo $NEW_ACTIVE > ~/$APP_NAME.active"
echo "Ensure old active stopped..."
ssh $TARGET_HOST "sudo systemctl stop $APP_NAME.$OLD_ACTIVE || exit 0"
echo -e "Done.\n\n"
upstream phoenix {
server localhost:ACTIVE_PORT;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name your.domain.com;
return 301 https://your.domain.com$request_uri;
}
server {
listen 443 ssl;
server_name your.domain.com;
root /home/ubuntu/APP_NAME.deployment/APP_NAME.ACTIVE/static;
client_max_body_size 1M;
keepalive_timeout 70;
ssl on;
ssl_certificate /etc/letsencrypt/live/your.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your.domain.com/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
try_files $uri @phoenix;
location ~ ^/(js|css)/ {
gzip_static on;
expires max;
add_header Cache-Control public;
add_header ETag "";
break;
}
location /live/websocket {
proxy_pass http://phoenix;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
location @phoenix {
proxy_intercept_errors on;
error_page 502 /502.html;
error_page 500 /500.html;
error_page 404 /404.html;
proxy_read_timeout 240;
proxy_send_timeout 240;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_pass http://phoenix;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment