An open API service indexing awesome lists of open source software.

https://github.com/cogini/mix-deploy-example

Example Elixir app which uses mix_systemd and mix_deploy to deploy
https://github.com/cogini/mix-deploy-example

elixir-application elixir-lang

Last synced: 3 months ago
JSON representation

Example Elixir app which uses mix_systemd and mix_deploy to deploy

Awesome Lists containing this project

README

        

# mix_deploy_example

This is a working example Elixir app which shows how to deploy using
[mix_deploy](https://github.com/cogini/mix_deploy) to a local system and via
[AWS CodeDeploy](https://aws.amazon.com/codedeploy/).

`mix_deploy` generates scripts which are used to deploy your app using systemd
on a server. It includes scripts to set up the initial system, deploy
code and handle configuration during startup. It uses
[mix_systemd](https://github.com/cogini/mix_systemd) to generate systemd unit
files.

# Deploying locally

These instructions show how to deploy an app to the same server you are building
on. That can be a $5/month [Digital Ocean](https://m.do.co/c/150575a88316) server.

## Install build dependencies

Install Erlang, Elixir and Node.js from OS packages:

```shell
# Ubuntu
LANG=en_US.UTF-8 sudo bin/build-install-deps-ubuntu

# CentOS
LANG=en_US.UTF-8 sudo bin/build-install-deps-centos
```

or install using [ASDF](https://www.cogini.com/blog/using-asdf-with-elixir-and-phoenix/):

```shell
# Ubuntu
LANG=en_US.UTF-8 sudo bin/build-install-asdf-deps-ubuntu && bin/build-install-asdf-init

# CentOS
LANG=en_US.UTF-8 sudo bin/build-install-asdf-deps-centos && bin/build-install-asdf-init
```

We normally use ASDF, but compiling from source on a small server takes a while
and may run out of RAM unless you adjust the config.

## Configure

This example loads environment vars from `/srv/mix-deploy-example/etc/environment`:

```elixir
config :mix_systemd,
# Run scripts before starting the app
exec_start_pre: [
# Run db migrations script /srv/mix-deploy-example/bin/deploy-migrate
[:deploy_dir, "/bin/deploy-migrate"],
],
dirs: [
# Create runtime temp dir /run/mix-deploy-example
:runtime,
],
env_files: [
# Load environment vars from /srv/mix-deploy-example/etc/environment
["-", :deploy_dir, "/etc/environment"],
],
env_vars: [
# Tell release scripts to use runtime directory for temp files
# Needed by config/releases.exs
["RELEASE_TMP=", :runtime_dir],
]

config :mix_deploy,
# Generate these scripts from templates
templates: [
# systemctl wrappers
"start",
"stop",
"restart",
"enable",

# System setup
"create-users",
"create-dirs",
"set-perms",

# Local deploy
"init-local",
"copy-files",
"release",
"rollback",

# Release commands
"set-env",
"remote-console",
"migrate",
],
# Match mix_systemd
env_files: [
["-", :deploy_dir, "/etc/environment"],
],
env_vars: [
# Tell release scripts to use runtime directory for temp files
["RELEASE_TMP=", :runtime_dir],
],
dirs: [
:runtime,
],
# Copy config/environment from project to /etc/mix-deploy-example/etc/environment
copy_files: [
%{
src: "config/environment",
dst: [:deploy_dir, "/etc/environment"],
user: "$DEPLOY_USER",
group: "$APP_GROUP",
mode: "640"
},
]
```

Set up your production db password and `secret_key_base`, used by Phoenix to protect
session cookies.

Generate `secret_key_base`:

```shell
mix phx.gen.secret 64
```

Create a database using
[Digital Ocean's Managed Databases Service](https://www.cogini.com/blog/multiple-databases-with-digital-ocean-managed-databases-service/)
and get the database connection URL.

Create the file `config/environment` with app secrets:

```shell
SECRET_KEY_BASE="EOdJB1T39E5Cdeebyc8naNrOO4HBoyfdzkDy2I8Cxiq4mLvIQ/0tK12AK1ahrV4y"
DATABASE_URL="ecto://doadmin:SECRET@db-postgresql-sfo2-xxxxx-do-user-yyyyyy-0.db.ondigitalocean.com:25060/defaultdb?ssl=true"
```

Add `config/environment` to `.gitignore`.

`bin/deploy-copy-files` copies `config/environment` to `/srv/mix-deploy-example/environment/etc`.
`systemd` then loads it on startup, setting OS environment vars.

Configure `config/releases.exs` to use `System.get_env/2` to read config from
the environment vars:

```elixir
config :mix_deploy_example, MixDeployExampleWeb.Endpoint,
http: [:inet6, port: System.get_env("PORT") || 4000],
secret_key_base: System.get_env("SECRET_KEY_BASE"),
cache_static_manifest: "priv/static/cache_manifest.json"

config :mix_deploy_example, MixDeployExample.Repo,
url: System.get_env("DATABASE_URL")
```

## Build the system

```shell
MIX_ENV=prod bin/build
```

In addition to the normal build stuff, that does the following:

```
mix systemd.init
MIX_ENV=prod mix systemd.generate

mix deploy.init
MIX_ENV=prod mix deploy.generate
chmod +x bin/*
```

Initialize the libraries, copying templates from `mix_systemd` and `mix_deploy`
package dirs to `rel/templates`, then generate files based on the config in
`config/prod.exs`:

## Initialize the local system

Set up the local system for the app, creating users, directories, etc:

sudo bin/deploy-init-local

THat does the following:

```shell
bin/deploy-create-users
bin/deploy-create-dirs

cp bin/* /srv/mix-deploy-example/bin

bin/deploy-copy-files
bin/deploy-enable
```

# Log out and log in again

The `bin/deploy-create-users` adds the deploy user to the group used by the
app. In order for that to take effect, you have to log out and log in again.

## Build

Build the app and make a release:

```shell
MIX_ENV=prod bin/build
```

## Deploy

Deploy the release to the local machine:

```shell
# Extract release to target directory, creating current symlink
bin/deploy-release

# Restart the systemd unit
sudo bin/deploy-restart
```

Check the status:
```shell
systemctl status mix-deploy-example
journalctl -f -u mix-deploy-example
```

## Test

Test it by making a request to the server:

```shell
curl -v http://localhost:4000/
```

If things aren't working right, you can roll back to the previous release:

```shell
bin/deploy-rollback
sudo bin/deploy-restart
```

# Preparing an existing project for deployment

Following are the steps used to set up this repo. You can do the same to add
it to your own project.

## Generate Phoenix project

```shell
mix phx.new mix_deploy_example
mix deps.get
cd assets && npm install && node node_modules/webpack/bin/webpack.js --mode development
```

- Add `mix.lock` to git
- Add `package-lock.json` to git

## Configure Elixir 1.9+ mix releases

Configure releases in `mix.exs`:

```elixir
defp releases do
[
prod: [
include_executables_for: [:unix],
steps: [:assemble, :tar]
],
]
end
```

Configure `rel/env.sh.eex` and `rel/vm.args.eex` if necessary, e.g.
to [increase network ports](https://www.cogini.com/blog/tuning-tcp-ports-for-your-phoenix-app/).

See [the docs](https://hexdocs.pm/mix/Mix.Tasks.Release.html) for more details.

## Install mix_deploy and mix_systemd

Add libraries to deps from Hex:

```elixir
{:mix_deploy, "~> 0.7"}
```

Add `rel/templates` and `bin/deploy-*` to `.gitignore`.

## Copy build and utility scripts

Copy scripts from the `bin/` directory to the `bin/` directory of your project.

These scripts install the required dependencies:

- `build-install-asdf`
- `build-install-asdf-deps-centos`
- `build-install-asdf-deps-ubuntu`
- `build-install-asdf-init`
- `build-install-asdf-macos`
- `build-install-deps-centos`
- `build-install-deps-ubuntu`

This script builds the app:

- `build`

This script verifies that the app is running correctly:

- `bin/validate-service`

## Configure Phoenix for OTP releases

Update `config/prod.exs` to run from release:

- Start Phoenix endpoints automatically

```elixir
config :phoenix, :serve_endpoints, true
```

- Don't import `prod.secret.exs`

```elixir
`# import_config "prod.secret.exs"`
```

## Configure mix_deploy and mix_systemd

Configure `mix_deploy` and `mix_systemd` in `config/prod.exs`.

## Configure ASDF

Create a `.tool-versions` file in the root of your project, describing the versions
of OTP, Elixir, and Node that you will be building with:

```
erlang 22.2
elixir 1.9.4
nodejs 10.15.3
```

## Configure for CodeDeploy

- Add `appspec.yml`

## Configure for CodeBuild

- Add `buildspec.yml`

## Add database migrations

- Add `lib/mix_deploy_example/release.ex` as described in
[Ecto migrations and custom commands](https://hexdocs.pm/phoenix/releases.html#ecto-migrations-and-custom-commands)

## Add TOML config provider

- Add to `mix.exs`

```elixir
defp deps do
[
{:toml_config, "~> 0.1.0"}, # Mix releases
]
end
```

```
defp releases do
[
aws: [
include_executables_for: [:unix],
config_providers: [
{TomlConfigProvider, path: "/etc/mix-deploy-example/config.toml"}
],
steps: [:assemble, :tar]
],
]
end
```

## Add Ansible scripts

See `ansible` dir.

## Add Docker file

```shell
build -f build/docker/Dockerfile .
```