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: Uses systemctl restart foo
  • systemd_flag: Touches a flag file to trigger a restart
  • touch: Like systemd_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

Informazioni sul progetto

Deploy an Elixir app

Installa
ansible-galaxy install cogini.elixir-release
Licenza
mit
Download
108
Proprietario
Product development services for ambitious innovators