Elixir/Phoenix deployments using Github Actions

7 months ago

One of the trickiest parts of getting Elixir services into production is the deployment. Common approaches include using Docker to generate containers that you can then deploy to AWS/Google/Azure, a Kubernetes cluster, or running the creation of the deployment locally (mix releases) and then publishing the finalised version to the target machine.

For my website https://pedroassuncao.com i decided to go with a simpler approach: Instead of building the final product in a local machine, creating a package (container or otherwise), and then deploying it to my host, i chose to do everything in the target system instead, by leveraging Github Actions.

5 Euro if you can guess what noozo means…

This approach has the following advantages:

  • Migrations don’t require a separate module to run (mix is available in the target system).
  • Deployment is fast due to cached dependencies on the target system and a simple SSH command running everything at once.
  • Slack notifications are super easy to integrate into Github actions and they are a fantastic way to keep track of deployment status.
  • Push and forget strategy for deployment is super easy.
  • No Docker and all its configuration mess.

Here's how i do it:

On my host (a simple Linode node) i installed Elixir, Erlang, NodeJS and the rest of the dependencies for any Elixir/Phoenix project. This has the disadvantage of having to manually upgrade their versions, but i can live with that by using asdf to manage them. I then proceeded to clone my repo into a folder on the host, where i will build, release, and control the service. This is done once, and has the advantage of caching dependencies directly on the target, which speeds up the deployment process.

Then, every commit i push to master (my code lives on Github, obviously), is caught by the following action:

name: Deploy
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
test_and_deploy:
runs-on: ubuntu-latest
steps:
- name: Slack Notification (Start)
env:
SLACK_BOT_TOKEN: ${{ secrets.slack_bot_token }}
uses: pullreminders/slack-action@master
with:
args: '{\"channel\":\"@nocivus\",\"text\":\"Starting website deployment...\"}'
  - name: Deploy To Linode
uses: appleboy/ssh-action@master
with:
host: pedroassuncao.com
username: <your_username>
key: ${{ secrets.ssh_key }}
port: 22
script_stop: true
script: |
echo "Sourcing nvm..."
. /home/deploy/.nvm/nvm.sh
echo "Pulling code..."
cd noozo_v2/pedroassuncao.com
git submodule update --remote
git pull --recurse-submodules origin master
echo "Updating mix deps..."
mix deps.get --only prod
echo "Setting up secrets..."
rm config/prod.secret.server.exs
ln -s ~/prod.secret.exs config/prod.secret.server.exs
echo "Compiling..."
MIX_ENV=prod mix compile
echo "Setting up assets..."
npm install --prefix assets
npm run deploy --prefix assets
MIX_ENV=prod mix phx.digest
echo "Releasing..."
MIX_ENV=prod mix release --overwrite
MIX_ENV=prod mix ecto.migrate
echo "Stopping server..."
_build/prod/rel/noozo/bin/noozo stop
sleep 5
echo "Starting server..."
_build/prod/rel/noozo/bin/noozo daemon
echo "All done."
  - name: Slack Notification (Success)
if: success()
env:
SLACK_BOT_TOKEN: ${{ secrets.slack_bot_token }}
uses: pullreminders/slack-action@master
with:
args: '{\"channel\":\"@nocivus\",\"text\":\"Website successfuly deployed :rocket:\"}'
  - name: Slack Notification (Fail)
if: failure()
env:
SLACK_BOT_TOKEN: ${{ secrets.slack_bot_token }}
uses: pullreminders/slack-action@master
with:
args: '{\"channel\":\"@nocivus\",\"text\":\"Website failed to deploy :dizzy_face:\"}'

As you might have noticed, i use Github secrets to inject the Slack webhooks and also the SSH key to connect to my target machine.

I'm pretty happy with this solution so far, however it's not without its flaws:

  • By not using restart the website is down for 5–10 seconds every deployment (there's probably a way around that, but last i tried it didn't work very well when the mix.exs version changes).
  • Elixir/Erlang/NodeJS versions need to be manually upgraded on the target host.

Let me know if there's anything i can improve, would love to hear your thoughts!

Happy Elixir'ing