mix_deploy_example
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.
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 server.
Install build dependencies
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.
Configure
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")
Build the system
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:
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:
MIX_ENV=prod bin/build
Deploy
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
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
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
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
:
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.
Install mix_deploy and mix_systemd
Add libraries to deps from Hex:
{: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
config :phoenix, :serve_endpoints, true
- Don't import
prod.secret.exs
`# 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
Add TOML config provider
- 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
Add Ansible scripts
See ansible
dir.
Add Docker file
build -f build/docker/Dockerfile .