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
ansibledirectory 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_appRelease 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.datafile 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: deployApp User:
elixir_release_app_user: "{{ elixir_release_service_name }}"Environment:
elixir_release_mix_env: prodBase Directory for Releases:
elixir_release_base_dir: /srvDirectories 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 foosystemd_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_systemdHTTP and HTTPS Ports:
elixir_release_http_listen_port: 4000 elixir_release_https_listen_port: 4001Open File Limit:
elixir_release_limit_nofile: 65536Restart Wait Time:
elixir_release_systemd_restart_sec: 5Language Setting:
elixir_release_lang: "en_US.UTF-8"File Creation Mask:
elixir_release_umask: "0027"Systemd Version:
elixir_release_systemd_version: 219Service Type:
elixir_release_service_type: simpleStart Command:
elixir_release_start_command: startPID 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
