This is a working example Elixir app which shows how to deploy using mix_deploy to a local system and via AWS 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 to generate systemd unit
files.
These instructions show how to deploy an app to the same server you are building on. That can be a $5/month Digital Ocean server.
Install Erlang, Elixir and Node.js from OS packages:
# 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:
# 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.
This example loads environment vars from /srv/mix-deploy-example/etc/environment
:
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
:
mix phx.gen.secret 64
Create a database using Digital Ocean's Managed Databases Service and get the database connection URL.
Create the file config/environment
with app secrets:
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:
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")
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
:
Set up the local system for the app, creating users, directories, etc:
sudo bin/deploy-init-local
THat does the following:
bin/deploy-create-users
bin/deploy-create-dirs
cp bin/* /srv/mix-deploy-example/bin
bin/deploy-copy-files
bin/deploy-enable
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 the app and make a release:
MIX_ENV=prod bin/build
Deploy the release to the local machine:
# Extract release to target directory, creating current symlink
bin/deploy-release
# Restart the systemd unit
sudo bin/deploy-restart
Check the status:
systemctl status mix-deploy-example
journalctl -f -u mix-deploy-example
Test it by making a request to the server:
curl -v http://localhost:4000/
If things aren't working right, you can roll back to the previous release:
bin/deploy-rollback
sudo bin/deploy-restart
Following are the steps used to set up this repo. You can do the same to add it to your own project.
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 releases in mix.exs
:
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.
See the docs for more details.
Add libraries to deps from Hex:
{:mix_deploy, "~> 0.7"}
Add rel/templates
and bin/deploy-*
to .gitignore
.
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
Update config/prod.exs
to run from release:
- Start Phoenix endpoints automatically
config :phoenix, :serve_endpoints, true
- Don't import
prod.secret.exs
`# import_config "prod.secret.exs"`
Configure mix_deploy
and mix_systemd
in config/prod.exs
.
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
- Add
appspec.yml
- Add
buildspec.yml
- Add
lib/mix_deploy_example/release.ex
as described in Ecto migrations and custom commands
- Add to
mix.exs
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
See ansible
dir.
build -f build/docker/Dockerfile .