{"id":18603601,"url":"https://github.com/cogini/mix_deploy","last_synced_at":"2025-10-06T03:32:05.959Z","repository":{"id":48425544,"uuid":"166534422","full_name":"cogini/mix_deploy","owner":"cogini","description":"Library of mix tasks to deploy an Elixir release to servers (cloud instance or bare metal, automated deploy)","archived":false,"fork":false,"pushed_at":"2024-07-11T08:42:08.000Z","size":303,"stargazers_count":132,"open_issues_count":2,"forks_count":14,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-09-14T11:35:04.571Z","etag":null,"topics":["codedeploy","elixir-lang","mix","mix-tasks"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cogini.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-01-19T10:30:17.000Z","updated_at":"2025-08-14T08:04:20.000Z","dependencies_parsed_at":"2024-05-30T01:31:52.773Z","dependency_job_id":"b5cbda4f-9d1a-4b1c-8480-714b63b66c83","html_url":"https://github.com/cogini/mix_deploy","commit_stats":{"total_commits":273,"total_committers":9,"mean_commits":"30.333333333333332","dds":0.08424908424908428,"last_synced_commit":"46ed4dafd0d33ea05b809789603fb3b9bd7d9cc9"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cogini/mix_deploy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cogini%2Fmix_deploy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cogini%2Fmix_deploy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cogini%2Fmix_deploy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cogini%2Fmix_deploy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cogini","download_url":"https://codeload.github.com/cogini/mix_deploy/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cogini%2Fmix_deploy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278555257,"owners_count":26006078,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-06T02:00:05.630Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codedeploy","elixir-lang","mix","mix-tasks"],"created_at":"2024-11-07T02:14:52.842Z","updated_at":"2025-10-06T03:32:05.918Z","avatar_url":"https://github.com/cogini.png","language":"Elixir","readme":"![test workflow](https://github.com/cogini/mix_systemd/actions/workflows/test.yml/badge.svg)\n[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)\n[![Module Version](https://img.shields.io/hexpm/v/mix_deploy.svg)](https://hex.pm/packages/mix_deploy)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/mix_deploy/)\n[![Total Download](https://img.shields.io/hexpm/dt/mix_deploy.svg)](https://hex.pm/packages/mix_deploy)\n[![License](https://img.shields.io/hexpm/l/mix_deploy.svg)](https://github.com/cogini/mix_deploy/blob/master/LICENSE.md)\n[![Last Updated](https://img.shields.io/github/last-commit/cogini/mix_deploy.svg)](https://github.com/cogini/mix_deploy/commits/master)\n\n# mix_deploy\n\n\nThis module generates scripts which help deploy an Erlang release, handling\ntasks such as creating initial directory structure, unpacking release files,\nmanaging configuration, and starting/stopping.  It supports deployment to the\nlocal machine, bare-metal servers, or cloud servers using e.g.,\n[AWS CodeDeploy](https://aws.amazon.com/codedeploy/).\n\nIt supports releases created with Elixir 1.9+\n[mix release](https://hexdocs.pm/mix/Mix.Tasks.Release.html)\nor [Distillery](https://hexdocs.pm/distillery/home.html).\n\nIt assumes that [mix_systemd](https://github.com/cogini/mix_systemd) is used to generate a\nsystemd unit file for the application, and shares conventions with it about naming files.\nSee [mix_systemd](https://github.com/cogini/mix_systemd) for examples.\n\nHere is [a complete example app](https://github.com/cogini/mix-deploy-example).\n\n## Installation\n\nAdd `mix_deploy` to the list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:mix_deploy, \"~\u003e 0.7\"},\n  ]\nend\n```\n\n## Example\n\nA straightforward way to deploy an app is on a virtual private server at, e.g.,\n[Digital Ocean](https://m.do.co/c/150575a88316), building and deploying on the\nsame machine. Check out the code on the server, run `mix compile` to build, run\n`mix test`, then run `mix release` to generate a release. You then use the\nscripts generated by `mix_release` to set up the runtime environment, deploy\nthe release to the target dir, and run it supervised by systemd.\n\n### Configure the app\n\nFollow the Phoenix config process for\n[deployment](https://hexdocs.pm/phoenix/deployment.html) and\n[releases](https://hexdocs.pm/phoenix/releases.html).\n\nThe app should read runtime configuration such as the database connection from\nenvironment variables in `config/runtime.exs`. Generate a production secret\nwith `mix phx.gen.secret`.\n\nCreate a file with these environment vars and put it in `config/environment`, e.g.:\n\n```shell\nDATABASE_URL=\"ecto://foo:Sekr!t@localhost/foo\"\nSECRET_KEY_BASE=\"VXR6/fViPssuoAyqmr0SvAYBIaMrtiZLaQCn1TfB5NXaOzssHxtegfF+yM+/Senv\"\n```\n\nAdd the `config/environment` file to `.gitignore` so that any secrets do not\nget checked into git.\n\n### Configure `mix_deploy` and `mix_systemd`\n\nConfigure `mix_deploy` and `mix_systemd` in `config/prod.exs`.\n\n`mix_systemd` generates a systemd unit file which loads the configuration\nfor the app. On startup, it creates the specified directories for the app in\nthe standard locations.\n\n```elixir\nconfig :mix_systemd,\n  env_files: [\n    # Read environment vars from file /srv/foo/etc/environment if it exists\n    [\"-\", :deploy_dir, \"/etc/environment\"],\n    # Read environment vars from file /etc/foo/environment if it exists\n    [\"-\", :configuration_dir, \"/environment\"]\n  ],\n  # Set individual env vars\n  env_vars: [\n    \"PHX_SERVER=true\"\n    \"PORT=8080\",\n  ],\n  # Create standard config dirs\n  dirs: [\n    # /var/cache/foo\n    :cache,\n    # /etc/foo\n    :configuration,\n    # /var/log/foo\n    :logs,\n    # /run/foo\n    :runtime,\n    # /var/lib/foo\n    :state,\n    # /var/tmp/foo\n    :tmp\n  ],\n  # Run app under this OS user, default is the app name\n  app_user: \"app\",\n  app_group: \"app\"\n```\n\n`mix_deploy` generates scripts to initialize the system and deploy it.\n\n```elixir\nconfig :mix_deploy,\n  app_user: \"app\",\n  app_group: \"app\"\n  # Copy config/environment to /etc/foo/environment\n  copy_files: [\n    %{\n      src: \"config/environment\",\n      dst: [:configuration_dir, \"/environment\"],\n      user: \"$DEPLOY_USER\",\n      group: \"$APP_GROUP\",\n      mode: \"640\"\n    },\n  ],\n  # Generate these scripts in bin\n  templates: [\n    \"init-local\",\n    \"create-users\",\n    \"create-dirs\",\n    \"copy-files\",\n    \"enable\",\n    \"release\",\n    \"restart\",\n    \"rollback\",\n    \"start\",\n    \"stop\",\n  ]\n```\n\n### Initialize `mix_systemd` and `mix_deploy` and generate files\n\n`mix_systemd` and `mix_deploy` generate output files from templates.\nRun the following to copy the templates into your project. The templating\nprocess most common needs via configuration, but you can also check them into\nyour project and make local modifications to handle special needs.\n\n```shell\nmix systemd.init\nmix deploy.init\n```\n\nGenerate output files:\n\n```shell\n# Create systemd unit file for app under _build/prod/systemd\nMIX_ENV=prod mix systemd.generate\n\n# Create deploy scripts project `bin` dir\nMIX_ENV=prod mix deploy.generate\nchmod +x bin/*\n```\n\n### Set up the system\n\nRun the scripts to set up the operating system for the deployment.\nThis creates the app OS user, directory structure under `/srv/foo`, and the\nsystemd unit file which supervises the app.\n\n`deploy-init-local` is a convenience script which runs other scripts to set up\nthe system:\n\n```shell\nsudo bin/deploy-init-local\n```\n\nIt does the following:\n\n```shell\n# Create users to run the app\nbin/deploy-create-users\n\n# Create deploy dirs under /srv/foo\nbin/deploy-create-dirs\n\n# Copy scripts used at runtime by the systemd unit\ncp bin/* /srv/foo/bin\n\n# Copy files and enable systemd unit\nbin/deploy-copy-files\nbin/deploy-enable\n```\n\n### Build the Elixir release\n\nCreate the Elixir (Erlang) release. This is a tar file containing the app, the\nlibraries it depends on, and the scripts to manage it.\n\n```shell\nMIX_ENV=prod mix release\n```\n\n### Deploy the release to the local machine:\n\n```shell\n# Extract release to target directory and make it current\nsudo bin/deploy-release\n\n# Restart the systemd unit\nsudo bin/deploy-restart\n```\n\nYou can roll back the release with the following:\n\n```shell\nbin/deploy-rollback\nsudo bin/deploy-restart\n```\n\nAdd an alias to `mix.exs`, and you can do the deploy by running `mix deploy`.\n\n```elixir\ndef project do\n  [\n    preferred_cli_env: [\n      deploy: :prod\n    ]\n  ]\nend\n\ndefp aliases do\n  [\n    deploy: [\n      \"release --overwrite\",\n      \"cmd sudo bin/deploy-release\",\n      \"cmd sudo bin/deploy-restart\"\n    ]\n  ]\nend\n```\n\n### Try it out\n\nYour app should now be running:\n\n```shell\ncurl -v http://localhost:8080/\n```\n\nIf it is not, have a look at the logs.\n\n```shell\nsystemctl status foo\njournalctl -u foo\n```\n\nIf you want it to run on port 80, you can [redirect 80 to 8080\nin the firewall](https://www.cogini.com/blog/port-forwarding-with-iptables/).\n\n## Usage\n\nFirst, use the `deploy.init` task to template files from the library to the\n`rel/templates/deploy` directory in your project.\n\n```shell\nmix deploy.init\n```\n\nNext, generate the scripts based on your project's config:\n\n```shell\nMIX_ENV=prod mix deploy.generate\nchmod +x bin/*\n```\n\nBy default, `mix deploy.generate` creates scripts under a `bin` directory at\nthe top level of your project. If you want to keep them separate, e.g. to\ncreate different files based on the environment, set `bin_dir` to\n`[:output_dir, \"bin\"]` and it will generate files under e.g. `_build/prod/deploy`.\n\n## Configuration\n\nThe library tries to choose smart defaults. It reads the app name from\n`mix.exs` and calculates default values for its configuration parameters.\n\nIf your app is named `foo_bar`, it will create a service named `foo-bar`,\ndeployed to `/srv/foo-bar`, running under the user `foo-bar`.\n\nThe library doesn't generate any output scripts by default, you need to enable\nthem with the `templates` parameter. It can create the following scripts:\n\n### Systemd scripts\n\nThese are wrappers on e.g. `/bin/systemctl restart foo`.  They are useful for\ne.g. CodeDeploy hook scripts where we have to run a script without parameters.\n\n* `deploy-start`: Start services\n* `deploy-stop`: Stop services\n* `deploy-restart`: Restart services\n* `deploy-enable`: Enable systemd units\n\n### System setup scripts\n\nThese scripts set up the target system for the application. They are useful for\nlocal and automated deploy.\n\n* `deploy-create-users`: Create OS accounts for app and deploy users\n* `deploy-create-dirs`: Create dirs, including the release dir `/srv/foo` and\n  standard dirs like `/etc/foo` if needed.\n\n### Local deploy scripts\n\nThese scripts deploy the app to the same server as it was built on:\n\n* `deploy-copy-files`: Copy files from `_build` to target `/srv/foo`, or to a\n  staging directory for packaging\n* `deploy-release`: Deploy release, extracting to a timestamped dir under\n  `/srv/foo/releases`, then making a symlink from `/srv/foo/current`\n* `deploy-rollback`: Rollback release, resetting the symlink to point to the\n  previous release\n\nThe library also has mix tasks to deploy and roll back releases:\n\n```shell\nmix deploy.local\nmix deploy.local.rollback\n```\n\n### CodeDeploy deploy scripts\n\nThese scripts run on the target machine as lifecycle hooks.\n\n* `deploy-clean-target`: Delete files under target dir\n   in preparation for deploying update\n* `deploy-extract-release`: Extract release from tar\n* `deploy-set-perms`: Set target file permissions so that they can be used by\n  the app user\n\n### Build server scripts\n\nThese scripts run on the build server.\n\n* `deploy-stage-files`: Copy output files to staging directory, default `files`\n\n### Release command scripts\n\nThese scripts set up the environment and then run release commands.\nThey make the config match the environment vars set at runtime in the systemd\nunit. With Elixir 1.9+ you can source `/srv/foo/bin/set-env` in `rel/env.sh.eex`.\nThe other scripts are mainly useful with Distillery.\n\n* `set-env`: Set up environment\n* `deploy-migrate`: Migrate database on target system by\n  [running a custom command](https://www.cogini.com/blog/running-ecto-migrations-in-a-release/).\n* `deploy-remote-console`: Launch remote console for the app\n\n### Runtime environment scripts\n\nThese scripts are called by the systemd unit to set get the application config\nat runtime prior to starting the app. They are more most useful with Distillery.\n\nElixir 1.9+ mix releases support\n[runtime configuration](https://hexdocs.pm/mix/Mix.Tasks.Release.html#module-runtime-configuration)\nvia `config/runtime.exs` and `rel/env.sh.eex`. It is more secure, however, to\nseparate the process of getting configuration from the app itself using\n[ExecStartPre](https://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStartPre=)]).\nSee [mix_systemd](https://github.com/cogini/mix_systemd) for examples.\n\n* `deploy-sync-config-s3`: Sync config files from S3 bucket to app `configuration_dir`\n* `deploy-runtime-environment-file`: Create `#{runtime_dir}/environment`\n  file on target from `cloud-init` metadata\n* `deploy-runtime-environment-wrap`: Get runtime environment from `cloud-init`\n  [metadata](https://cloudinit.readthedocs.io/en/latest/topics/instancedata.html),\n  set environment vars, then launch main script.\n* `deploy-set-cookie-ssm`: Get Erlang VM cookie from [AWS SSM Parameter\n  Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-paramstore.html)\n  and write to file.\n\nThe most useful of these is `deploy-sync-config-s3`, the rest are code you might copy into\n`rel/env.sh.eex`.\n\n### Dependencies\n\nThe generated scripts are mostly straight bash, with minimal dependencies.\n\n* `deploy-sync-config-s3` uses the [AWS CLI](https://aws.amazon.com/cli/)\n  to copy files from an S3 bucket.\n* `deploy-runtime-environment-file` and `deploy-runtime-environment-wrap` use\n   [jq](https://stedolan.github.io/jq/) to parse the\n   [cloud-init](https://cloud-init.io/) JSON file.\n* `deploy-set-cookie-ssm` uses the AWS CLI and `jq` to interact with\n  Systems Manager Parameter Store.\n\nTo install `jq` on Ubuntu:\n\n```shell\napt-get install jq\n```\n\nTo install the AWS CLI from the OS package manager on Ubuntu:\n\n```shell\napt-get install awscli\n```\n\n## Scenarios\n\n### CodeDeploy\n\nThe library can generate lifecycle hook scripts for use with a\ndeployment system such as [AWS CodeDeploy](https://aws.amazon.com/codedeploy/).\n\n```elixir\nconfig :mix_deploy,\n  app_user: \"app\",\n  app_group: \"app\",\n  templates: [\n    \"stop\",\n    \"create-users\",\n    \"create-dirs\",\n    \"clean-target\",\n    \"extract-release\",\n    \"set-perms\",\n    \"migrate\",\n    \"enable\",\n    \"start\",\n    \"restart\",\n  ],\n    ...\n```\n\nHere is an example\n[appspec.yml](https://docs.aws.amazon.com/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html)\nfile:\n\n```yaml\nversion: 0.0\nos: linux\nfiles:\n  - source: bin\n    destination: /srv/foo/bin\n  - source: systemd\n    destination: /lib/systemd/system\n  - source: etc\n    destination: /srv/foo/etc\nhooks:\n  ApplicationStop:\n    - location: bin/deploy-stop\n      timeout: 300\n  BeforeInstall:\n    - location: bin/deploy-create-users\n    - location: bin/deploy-create-dirs\n    - location: bin/deploy-clean-target\n  AfterInstall:\n    - location: bin/deploy-extract-release\n    - location: bin/deploy-set-perms\n    - location: bin/deploy-enable\n  ApplicationStart:\n    - location: bin/deploy-migrate\n      runas: app\n      timeout: 300\n    - location: bin/deploy-start\n      timeout: 3600\n  # ValidateService:\n    - location: bin/validate-service\n      timeout: 300\n```\n\n### Staging\n\nBy default, the scripts deploy the scripts as the same OS user that runs the\n`mix deploy.generate` command, and run the app under an OS user with the same\nname as the app.\n\nMany scripts allow you to override environment variables at execution time. For\nexample, you can override the user accounts which own the files by setting the\nenvironment vars `APP_USER`, `APP_GROUP`, and `DEPLOY_USER`.\n\nSimilarly, set `DESTDIR` and the copy script will add a prefix when copying\nfiles. This lets you copy files to a staging directory, tar it up, then extract\nit on a target machine, e.g.:\n\n```shell\nmkdir -p ~/tmp/deploy\nDESTDIR=~/tmp/deploy bin/deploy-create-dirs\nDESTDIR=~/tmp/deploy bin/deploy-copy-files\n```\n\n## Configuration options\n\nThe following sections describe common configuration options.\nSee `lib/mix/tasks/deploy.ex` for details of more obscure options.\n\nIf you need to make changes not supported by the config options,\nthen you can check the templates in `rel/templates/deploy`\ninto source control and make your own changes. Contributions are welcome!\n\nThe list of templates to generate is in the `templates` config var.\nYou can modify this list to remove scripts, and they won't be generated.\nYou can also add your own scripts and they will be run as templates with the\nconfig vars defined.\n\n### Basics\n\n`app_name`: Elixir application name, an atom, from the `app` field in the `mix.exs` project.\n\n`version`: `version` field in `mix.exs` project.\n\n`module_name`: Elixir camel case module name version of `app_name`, e.g. `FooBar`.\n\n`release_name`: Name of release, default `app_name`.\n\n`ext_name`: External name, used for files and directories,\ndefault `app_name` with underscores converted to \"-\", e.g. `foo-bar`.\n\n`service_name`: Name of the systemd service, default `ext_name`.\n\n`release_system`: `:mix | :distillery`, default `:mix`\n\nIdentifies the system used to generate the releases,\n[Mix](https://hexdocs.pm/mix/Mix.Tasks.Release.html) or\n[Distillery](https://hexdocs.pm/distillery/home.html).\n\n### Users\n\n`deploy_user`: OS user account that is used to deploy the app, e.g. own the\nfiles and restart it. For security, this is separate from `app_user`, keeping\nthe runtime user from being able to modify the source files. Defaults to the\nuser running the script, supporting local deploy. For remote deploy, set this\nto a user like `deploy` or same as the app user.\n\n`deploy_group`: OS group account, default `deploy_user`.\n\n`app_user`: OS user account that the app should run under. Default `deploy_user`.\n\n`app_group`: OS group account, default `deploy_group`.\n\n### Directories\n\n`base_dir`: Base directory for app files on target, default `/srv`.\n\n`deploy_dir`: Directory for app files on target, default `#{base_dir}/#{ext_name}`.\n\nWe use the\n[standard app directories](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RuntimeDirectory=),\nfor modern Linux systems. App files are under `/srv`, configuration under\n`/etc`, transient files under `/run`, data under `/var/lib`.\n\nDirectories are named based on the app name, e.g. `/etc/#{ext_name}`.\nThe `dirs` variable specifies which directories the app uses.\nBy default, it doesn't set up anything. To enable them, configure `dirs`, e.g.:\n\n```elixir\ndirs: [\n  # :runtime,       # App runtime files which may be deleted between runs, /run/#{ext_name}\n  # :configuration, # App configuration, e.g. db passwords, /etc/#{ext_name}\n  # :state,         # App data or state persisted between runs, /var/lib/#{ext_name}\n  # :cache,         # App cache files which can be deleted, /var/cache/#{ext_name}\n  # :logs,          # App external log files, not via journald, /var/log/#{ext_name}\n  # :tmp,           # App temp files, /var/tmp/#{ext_name}\n],\n```\n\nRecent versions of systemd (since 235) will create these directories at\nstart time based on the settings in the unit file. For earlier systemd\nversions, `deploy-create-dirs` will create them.\n\nFor security, we set permissions to 750, more restrictive than the systemd\ndefaults of 755. You can configure them with variables like\n`configuration_directory_mode`. See the defaults in\n`lib/mix/tasks/deploy.ex`.\n\n`systemd_version`: Sets the systemd version on the target system, default 235.\nThis determines which systemd features the library will enable. If you are\ntargeting an older OS release, you may need to change it. Here are the systemd\nversions in common OS releases:\n\n* CentOS 7: 219\n* Ubuntu 16.04: 229\n* Ubuntu 18.04: 237\n\n### Additional directories\n\nThe library uses a directory structure under `deploy_dir` which supports\nmultiple releases, similar to [Capistrano](https://capistranorb.com/documentation/getting-started/structure/).\n\n* `scripts_dir`: deployment scripts which e.g. start and stop the unit, default `bin`.\n* `current_dir`: where the current Erlang release is unpacked or referenced by symlink, default `current`.\n* `releases_dir`: where versioned releases are unpacked, default `releases`.\n* `flags_dir`: dir for flag files to trigger restart, e.g. when `restart_method` is `:systemd_flag`, default `flags`.\n\nWhen using multiple releases and symlinks, the deployment process works as follows:\n\n1. Create a new directory for the release with a timestamp like\n   `/srv/foo/releases/20181114T072116`.\n\n2. Upload the new release tarball to the server and unpack it to the releases dir\n\n3. Make a symlink from `/srv/#{ext_name}/current` to the new release dir.\n\n4. Restart the app.\n\nIf you are only keeping a single version, then deploy it to the directory\n`/srv/#{ext_name}/current`.\n\n## Variable expansion\n\nThe following variables support variable expansion:\n\n```elixir\nexpand_keys: [\n  :env_files,\n  :env_vars,\n  :runtime_environment_service_script,\n  :conform_conf_path,\n  :pid_file,\n  :root_directory,\n  :bin_dir,\n]\n```\n\nYou can specify values as a list of terms, and it will look up atoms as keys in\nthe config. This lets you reference e.g. the deploy dir or configuration dir without\nhaving to specify the full path, e.g. `[\"!\", :deploy_dir, \"/bin/myscript\"]` gets\nconverted to `\"!/srv/foo/bin/myscript\"`.\n\n### Environment vars\n\nConfig vars set a few common env vars:\n\n* `mix_env`: default `Mix.env()`, sets `MIX_ENV`\n* `env_lang`: default `en_US.utf8`, used to set `LANG`\n\nIn addition, you can set `env_vars` and `env_files` the same way\nas for `mix_systemd`. The `set-env` script will then set these\nvariables the same way as they are in the systemd unit,\nallowing you to run release commands with the same config, e.g. database\nmigrations or console. It also sets:\n\n* `RUNTIME_DIR`: `runtime_dir`, if `:runtime` in `dirs`\n* `CONFIGURATION_DIR`: `configuration_dir`, if `:configuration` in `dirs`\n* `LOGS_DIR`: `logs_dir`, if `:logs` in `dirs`\n* `CACHE_DIR`: `cache_dir`, if `:cache` in `dirs`\n* `STATE_DIR`: `state_dir`, if `:state` in `dirs`\n* `TMP_DIR`: `tmp_dir`, if `:tmp` in `dirs`\n\nYou can set additional vars using `env_vars`, e.g.:\n\n```elixir\nenv_vars: [\n  \"PORT=8080\",\n]\n```\nYou can also reference the value of other parameters by name, e.g.:\n\n```elixir\nenv_vars: [\n  [\"RELEASE_TMP=\", :runtime_dir],\n]\n```\n\nYou can read environment vars from files with `env_files`, e.g.:\n\n```elixir\nenv_files: [\n  [\"-\", :deploy_dir, \"/etc/environment\"],\n  [\"-\", :configuration_dir, \"environment\"],\n  [\"-\", :runtime_dir, \"environment\"],\n],\n```\n\nThe \"-\" at the beginning makes the file optional, the system will start without them.\nLater values override earlier values, so you can set defaults in the release which get\noverridden in the deployment or runtime environment.\n\nWith Distillery, you can generate a file under the release with an overlay in\n`rel/config.exs`, e.g.:\n\n```elixir\nenvironment :prod do\n  set overlays: [\n    {:mkdir, \"etc\"},\n    {:copy, \"rel/etc/environment\", \"etc/environment\"},\n    # {:template, \"rel/etc/environment\", \"etc/environment\"}\n  ]\nend\n```\n\nThat results in a file that would be read by:\n\n```elixir\nenv_files: [\n  [\"-\", :current_dir, \"/etc/environment\"],\n],\n```\n\n### Starting and restarting\n\nThe following variables set systemd variables:\n\n`service_type`: `:simple | :exec | :notify | :forking`. systemd\n[Type](https://www.freedesktop.org/software/systemd/man/systemd.service.html#Type=), default `:simple`.\n\nModern applications don't fork, they run in the foreground and\nrely on the supervisor to manage them as a daemon. This is done by setting\n`service_type` to `:simple` or `:exec`. Note that in `simple` mode, systemd\ndoesn't actually check if the app started successfully, it just continues\nstarting other units. If something depends on your app being up, `:exec` may be\nbetter.\n\nSet `service_type` to `:forking`, and the library sets `pid_file` to\n`#{runtime_directory}/#{app_name}.pid` and sets the `PIDFILE` env var to tell\nthe boot scripts where it is.\n\nThe Erlang VM runs pretty well in foreground mode, but traditionally runs as\nas a standard Unix-style daemon, so forking might be better. Systemd\nexpects foregrounded apps to die when their pipe closes. See\nhttps://elixirforum.com/t/systemd-cant-shutdown-my-foreground-app-cleanly/14581/2\n\n`restart_method`: `:systemctl | :systemd_flag | :touch`, default `:systemctl`\n\nThe normal situation is that the app will be restarted using e.g.\n`systemctl restart foo`.\n\nWith `:systemd_flag`, an additional systemd unit file watches for\nchanges to a flag file and restarts the main unit. This allows updates to be\npushed to the target machine by an unprivileged user account which does not\nhave permissions to restart processes. Touch the file `#{flags_dir}/restart.flag`\nand systemd will restart the unit.  See `mix_systemd` for details.\n\nWith `:touch`, the app itself watches the file `#{flags_dir}/restart.flag`.\nIf it changes, the app shuts itself down, relying on systemd to notice and restart it.\n\n`sudo_deploy`: Creates `/etc/sudoers.d/#{ext_name}` file which allows the deploy\nuser to start/stop/restart the app using sudo. Default `false`. Note that\nwhen you must call systemctl with the full path, e.g. `sudo /bin/systemctl restart foo`\nfor this to work.\n\n`sudo_app`: Creates `/etc/sudoers.d/#{ext_name}` file allowing the app user\nuser to start/stop/restart the app using sudo. Default `false`.\n\n### Configuration examples\n\nHere is a complete example of configuring an app from a config file which\nit pulls from S3 on startup.\n\nWe set up an `ExecStartPre` command in the systemd unit file which runs\n`deploy-sync-config-s3` before starting the app. It runs the AWS cli command:\n\n```shell\naws s3 sync \"s3://${CONFIG_S3_BUCKET}/${CONFIG_S3_PREFIX}\" \"${CONFIG_DIR}/\"\n```\n\n`CONFIG_S3_BUCKET` is the source bucket, and `CONFIG_S3_PREFIX` is an optional\npath in the bucket. `CONFIG_DIR` is the app configuration dir on the target\nsystem, `/etc/foo`.\n\nWe need to bootstrap the config process, so we use a different environment file\nfrom the main config.\n\n```shell\nmkdir -p rel/etc\necho \"CONFIG_S3_BUCKET=cogini-foo-dev-app-config\" \u003e\u003e rel/etc/environment\n```\n\nSet `exec_start_pre` in the `mix_systemd` config:\n\n```elixir\nconfig :mix_systemd,\n  app_user: \"app\",\n  app_group: \"app\",\n  # systemd runs this before starting the app as root\n  exec_start_pre: [\n    [\"!\", :deploy_dir, \"/bin/deploy-sync-config-s3\"]\n  ],\n  dirs: [\n    # Create /etc/foo\n    :configuration,\n    # Create /run/foo\n    :runtime,\n  ],\n  # systemd should not clean up /run/foo\n  runtime_directory_preserve: \"yes\",\n  # Load env from /srv/foo/etc/environment and /etc/foo/environment\n  env_files: [\n    [\"-\", :deploy_dir, \"/etc/environment\"],\n    [\"-\", :configuration_dir, \"/environment\"],\n  ],\n  # deploy-copy-files will copy the env file to /srv/foo/etc\n  # more likely it is done by e.g. appspec.yml\n  copy_files: [\n    %{\n      src: \"rel/etc/environment\",\n      dst: [:deploy_dir, \"/etc\"],\n      user: \"$DEPLOY_USER\",\n      group: \"$APP_GROUP\",\n      mode: \"640\"\n    },\n  ],\n  env_vars: [\n    # Temp files are in /run/foo\n    [\"RELEASE_TMP=\", :runtime_dir],\n  ]\n\nconfig :mix_deploy,\n  app_user: \"app\",\n  app_group: \"app\"\n  templates: [\n    \"init-local\",\n    \"create-users\",\n    \"create-dirs\",\n    \"copy-files\",\n    \"enable\",\n    \"release\",\n    \"restart\",\n    \"rollback\",\n    \"start\",\n    \"stop\",\n\n    \"sync-config-s3\",\n  ],\n  dirs: [\n    :configuration,\n    :runtime,\n  ],\n  # Set env config in e.g. deploy-set-env to match above.\n  env_files: [\n    [\"-\", :deploy_dir, \"/etc/environment\"],\n    [\"-\", :configuration_dir, \"/environment\"],\n  ]\n  env_vars: [\n    [\"RELEASE_TMP=\", :runtime_dir],\n  ]\n```\n\nFor security, the app only has read-only access to its config files, and\n`/etc/foo` has ownership `deploy:foo` and mode 750. We prefix the command\nwith \"!\" so it runs with elevated permissions, not as the `foo` user.\n\nWe need to set the `CONFIG_S3_BUCKET` variable in the environment so that\n`deploy-sync-config-s3` can use it. We can set it in `env_vars`\nor put it in the file `/etc/foo/environment`.\n\n* `/srv/foo/etc/environment` settings are configured at deploy time.\n* `/etc/foo/environment` settings might come from an S3\nbucket.\n* `/run/foo/environment` settings might be generated dynamically, e.g. getting\n  the IP address.\n\nFor example, `post_build` commands in the CodeBuild CI `buildspec.yml` file\ncan generate a config file `files/etc/environment`:\n\n```yaml\npost_build:\n  commands:\n    - mkdir -p files/etc\n    - echo \"CONFIG_S3_BUCKET=$BUCKET_CONFIG\" \u003e\u003e files/etc/environment\n```\n\nThen the CodeDeploy `appspec.yml` copies it to the target system under `/srv/foo/etc`:\n\n```yaml\nfiles:\n  - source: bin\n    destination: /srv/foo/bin\n  - source: systemd\n    destination: /lib/systemd/system\n  - source: etc\n    destination: /srv/foo/etc\n```\n\nSee [mix_systemd](https://github.com/cogini/mix_systemd) for more examples.\n\nI am `jakemorrison` on on the Elixir Slack and Discord, `reachfh` on\nFreenode `#elixir-lang` IRC channel. Happy to chat or help with\nyour projects.\n\n## Copyright and License\n\nCopyright (c) 2019 Jake Morrison\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcogini%2Fmix_deploy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcogini%2Fmix_deploy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcogini%2Fmix_deploy/lists"}