cogini.elixir-release
elixir-release
This Ansible role helps you deploy Elixir/Phoenix applications.
It uses Erlang "releases" with systemd to keep the application running smoothly, as explained in the articles:
Directory Structure
The release files are organized like Capistrano does. The main directory is named after your app, for example, /srv/foo
, which contains a releases
directory. When you deploy, a new directory is created with a timestamp, like /srv/foo/releases/20190603T072116
, where the files are unpacked. A symlink is created from /srv/foo/current
to point to the new directory.
Restarting the App
After deploying the release, the app is restarted to go live.
By default, with the setting elixir_release_restart_method: systemctl
, it restarts the app using this command:
sudo /bin/systemctl restart foo
Make sure the deploy user has permission to restart the app. Instead of giving full sudo permissions, create a specific sudo config file like /etc/sudoers.d/deploy-foo
:
deploy ALL=(ALL) NOPASSWD: /bin/systemctl start foo, /bin/systemctl stop foo, /bin/systemctl restart foo
A better option is to avoid needing sudo entirely by using systemd’s features. Set elixir_release_restart_method: systemd_flag
, and after deploying, a file /srv/foo/flags/restart.flag
gets touched, prompting systemd to restart the app with the new code.
Check out mix-deploy-example for a full example.
Example Playbook
Here’s a simple playbook for an app called foo
:
- hosts: '*'
become: true
vars:
elixir_release_app_name: foo
roles:
- cogini.elixir-release
Place this in the ansible/playbooks/deploy-app.yml
file.
First, set up the target machine, such as by installing required packages and creating necessary directories. Run this from your development machine using a user with sudo rights:
ansible-playbook -u $USER -v -l web_servers playbooks/deploy-app.yml --skip-tags deploy -D
Next, deploy the code. Run the following from the build server under a user with SSH access to the deploy account on the target machine:
ansible-playbook -u deploy -v -l web_servers playbooks/deploy-app.yml --tags deploy --extra-vars ansible_become=false -D
Here’s a more customized playbook:
- hosts: '*'
become: true
vars:
elixir_release_app_name: foo
elixir_release_app_user: bar
elixir_release_deploy_user: deploy
elixir_release_mix_env: frontend
elixir_release_systemd_source: mix_systemd
elixir_release_base_dir: /opt/bar
elixir_release_app_dirs:
- configuration
- runtime
- logs
- tmp
- state
- cache
elixir_release_tmp_directory_base: /var/tmp/bar
elixir_release_state_directory_base: /var/bar
elixir_release_http_listen_port: 8080
elixir_release_cache_directory_mode: 0700
elixir_release_configuration_directory_mode: 0755
elixir_release_logs_directory_mode: 0755
elixir_release_state_directory_mode: 0755
elixir_release_tmp_directory_mode: 0755
elixir_release_sudoers_file: "{{ elixir_release_app_user }}-{{ elixir_release_service_name }}"
elixir_release_src_dir: "{{ playbook_dir }}/../../../foo"
roles:
- cogini.elixir-release
Role Variables
Building Releases: Specify whether to use "mix" or "distillery" for releases:
elixir_release_release_system: "mix"
App Directory: Where release files are located. By default, it looks for an
ansible
directory in your app source:elixir_release_app_dir: "{{ role_path }}/../../.."
App Name: Erlang name for the application, which Distillery uses for naming:
elixir_release_app_name: my_app
Release Name: Usually the app name or the environment name:
elixir_release_release_name: "{{ elixir_release_app_name }}"
Service Name: Used for systemd and directories:
elixir_release_service_name: "{{ elixir_release_app_name | replace('_', '-') }}"
App Version: Version to be released. It will read this from the
start_erl.data
file if not specified:elixir_release_version: "0.1.0"
Separate accounts for deployment and app running increase security. The deployment account usually called deploy
owns the code, while the app itself runs under a different account with minimal permissions, often named after the app (like foo
) or a generic name like app
.
The release files are owned by deploy:app
with a mode of 0644
for read access by the app.
User Accounts:
Deploy User:
elixir_release_deploy_user: deploy
App User:
elixir_release_app_user: "{{ elixir_release_service_name }}"
Environment:
elixir_release_mix_env: prod
Base Directory for Releases:
elixir_release_base_dir: /srv
Directories for Deploy Files:
elixir_release_deploy_dir: "{{ elixir_release_base_dir }}/{{ elixir_release_service_name }}"
Release Directory:
elixir_release_releases_dir: "{{ elixir_release_deploy_dir }}/releases"
Current Release Directory:
elixir_release_current_dir: "{{ elixir_release_deploy_dir }}/current"
Scripts Directory:
elixir_release_scripts_dir: "{{ elixir_release_deploy_dir }}/bin"
Flags Directory:
elixir_release_flags_dir: "{{ elixir_release_deploy_dir }}/flags"
Restart Method Options:
systemctl
: Usessystemctl restart foo
systemd_flag
: Touches a flag file to trigger a restarttouch
: Likesystemd_flag
, touches a flag file for restarts
Which users can restart the app with sudo using systemctl
:
elixir_release_restart_users:
- "{{ elixir_release_deploy_user }}"
Set this to allow or restrict users as needed.
Systemd and Scripts
This role assumes you're using mix_systemd
to generate systemd unit files and mix_deploy
for lifecycle scripts.
Source for systemd unit file:
elixir_release_systemd_source: mix_systemd
HTTP and HTTPS Ports:
elixir_release_http_listen_port: 4000 elixir_release_https_listen_port: 4001
Open File Limit:
elixir_release_limit_nofile: 65536
Restart Wait Time:
elixir_release_systemd_restart_sec: 5
Language Setting:
elixir_release_lang: "en_US.UTF-8"
File Creation Mask:
elixir_release_umask: "0027"
Systemd Version:
elixir_release_systemd_version: 219
Service Type:
elixir_release_service_type: simple
Start Command:
elixir_release_start_command: start
PID File for Forking:
elixir_release_pid_file: "{{ elixir_release_runtime_dir }}/{{ elixir_release_app_name}}.pid"
Pre-start Scripts:
elixir_release_exec_start_pre: []
Environment Variables in Unit File:
elixir_release_env_vars: []
Dependencies
No additional dependencies are required.
Requirements
No special requirements.
License
This project is licensed under the MIT License.
Author Information
Jake Morrison jake@cogini.com