Skip to content

thiagoesteves/deployex

Repository files navigation

DeployEx

Who supervises the supervisor (of your application)?

Development Build Status

DeployEx is a lightweight tool designed for managing deployments for Beam applications (Elixir, Gleam and Erlang) without relying on additional deployment tools like Docker or Kubernetes. Its primary goal is to utilize the release package for executing full deployments or hot-upgrades, depending on the package's content, while leveraging OTP distribution for monitoring and data extraction.

DeployEx acts as a central deployment runner, gathering crucial deployment data such as the current version and release package contents. The content of the release package enables it to run for a full deployment or a hot-upgrade. Meanwhile, on the development front, your CI/CD pipeline takes charge of crafting and updating packages for the target release. This integration ensures that DeployEx is always equipped with the latest packages, ready to facilitate deployments.

To learn more about DeployEx and the advantages of removing containerized abstractions from your deployment process, check out the talk Bring back the power of Beam deployments with DeployEx at CODE BEAM America 2025.

DeployEx is currently used by:

Deployment Architecture

Upon deployment, the following dashboard becomes available, providing easy access to logs, the terminal, process observability, and much more for both DeployEx and the monitored applications.

Deployex Monitoring ▶️ See DeployEx in action

🔉 Features

  • Monitors multiple Beam application nodes (Elixir/Erlang/Gleam) and automatically restarts them if they crash for any reason.
  • Includes a backoff delay restart mechanism to prevent excessive restarts.
  • Performs full deployments based solely on the release files generated by:
    • mix release for Elixir.
    • gleam export for Gleam.
    • rebar3 as prod tar for Erlang.
  • Supports hot code reloading for:
  • Supports the following cloud providers (with terraform files):
    • Amazon Web Services (AWS)
    • Google Cloud Provisioning (GCP)
  • Provides rollback functionality if a monitored app version remains unstable for 10 minutes.
  • Rolled-back monitored app versions are ghosted, preventing their redeployment.
  • Ensures all nodes remain connected to the OTP distribution, including DeployEx itself.
  • Supports OTP distribution with mutual TLS (mTLS) for secure monitoring of apps and DeployEx.
  • Provides the ability to run pre-commans prior deployments for Database migrations or any other eval command.
  • Provides a friendly UI that only authenticated users can access.
  • Allows setting a previously configured version in the UI, enabling DeployEx to enforce deployment of a specific version.
  • Supports individual application restarts via the UI, including DeployEx itself.
  • Provides easy access to the application shell:
    • IEx shell for monitored Elixir apps and DeployEx.
    • Erlang shell for monitored Gleam/Erlang apps.
  • Provides monitoring and restart mechanisms for host memory thresholds and beam statistics of monitored applications, including port, atom, and process metrics.
  • Supports access to live log files (stdout and stderr) for both monitored apps and DeployEx.
  • Supports observability for all connected applications via Observer Web.
  • Supports safe tracing for all connected applications.
  • Provides visualization of Host System memory and CPU usage.
  • Provides easy access to the host shell (tmux).
  • Provides installer script to be used with ubuntu hosts.
  • Provides status information per node:
    • OTP connectivity
    • Version history
    • Last deployment status
    • Number of crash restarts
    • Number of forced restarts
  • And much more ...

Note

All examples and deployments in this project use NGINX as a reverse proxy and load balancer. However, DeployEx does not depend on NGINX; it is used here purely for convenience.

🚀 New Monitoring Feature!

Your application is now better protected from crashes caused by excessive memory usage, port/atom exhaustion, or too many processes.

🧠 Curious why monitoring memory is essential?
Check out this must-watch video:
▶️ Battling Memory Leaks: Tales from the Trenches at WhatsApp

⚠️ Next steps

What is coming next

  • ISSUE-110 - Add Health Check via OTP distribution
  • ISSUE-111 - Add support for secrets via Environment vars
  • ISSUE-112 - Add support for hot upgrading DeployEx

📁 Getting Started

Since OTP distribution is heavily used between the DeployEx and Monitored Applications, users must ensure that both applications are running the same OTP Major version to prevent compatibility issues and system malfunctions. DeployEx releases will contain binaries for the following OTP versions:

DeployEx version [1]
0.5.2 26.2.5.12 27.3.4 28.0 [2]
0.5.1 26.2.5.12 27.3.4 -/-
0.5.0 26.2.5.12 27.3.4 -/-
0.4.2 26.2.5.12 27.3.3 -/-
0.4.1 26.2.5.10 27.3.3 -/-
0.4.0 26.2.5.10 27.3.3 -/-
0.3.4 26.2.5.10 -/- -/-
0.3.3 26.2.5.6 -/- -/-
0.3.2 26.2.5.6 -/- -/-
0.3.1 26.2.5.6 -/- -/-
0.3.0 26.2.5.6 -/- -/-
  • [1] - Binaries for OTP-26 will be deprecated soon since new releases of erlexec won't support OTP-26
  • [2] - Binaries for OTP-28 are compiled with Elixir 1.18.4-otp-27

Running the application

You can kickstart the setup with the following commands, the default number of replicas is 3:

mix deps.get
iex --sname deployex --cookie cookie -S mix phx.server
Erlang/OTP 27 [erts-15.2.7] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit]

[info] Initializing Host Memory Server
[info] Initializing Engine Server for myphoenixapp
[info] Initializing Watchdog Server
[info] Running DeployexWeb.Endpoint with Bandit 1.6.11 at 127.0.0.1:5001 (http)
[info] Access DeployexWeb.Endpoint at http://localhost:5001
Interactive Elixir (1.17.0) - press Ctrl+C to exit (type h() ENTER for help)
[watch] build finished, watching for changes...

Rebuilding...

Done in 993ms.
[error] Error downloading release version for myphoenixapp, reason: {:error, :enoent}
[warning] No versions set yet for myphoenixapp

Now you can visit localhost:5001 from your browser and enter the credentials for the admin user, username: admin password: deployex. You should expect the following dashboard:

Empty Dashboard

Note

The error message in the CLI is due to no monitored app is available to be deployed. If you want to proceed for a local test, follow the steps at Running DeployEx and Monitored app locally. Also, it is important to note that the distribution will be required so this is the reason to add -sname deployex in the command

How DeployEx handles monitored application Version/Release

The DeployEx app expects a current.json file to be available, which contains version, hash information and any pre-command. This file is mandatory for full deployment and hot upgrades.

Version file (current.json)

Expected location in the release folder:

# production path
./{bucket}/versions/{monitored_app}/{env}/current.json
# local test path
/tmp/deployex/bucket/versions/{monitored_app}/{env}/current.json

Expected JSON format for current.json:

{
  "version": "1.0.0",
  "pre_commands": [ "eval MyApp.Migrator.create", "eval MyApp.Migrator.migrate" ], // optional field
  "hash": "local"
}

Once the file is captured, the deployment will start if no app is running or if the current app is running with a version that differs from the current.json file.

Release package

Expected location in the release folder:

# production path
./{bucket}/dist/{monitored_app}/{monitored_app}-{version}.tar.gz
# local test path
/tmp/deployex/bucket/dist/{monitored_app}/{monitored_app}-{version}.tar.gz

🛠️ Production Information

Environment Variables

DeployEx application typically requires a few environment variables to be defined for proper operation. Ensure that you have the following environment variables set when running in production:

ENV NAME EXAMPLE SOURCE DESCRIPTION
DEPLOYEX_SECRET_KEY_BASE 42otsNl...Fpq3dIJ02 aws or gcp secrets secret key used for encryption
DEPLOYEX_ERLANG_COOKIE cookie aws or gcp secrets erlang cookie
DEPLOYEX_ADMIN_HASHED_PASSWORD 2b1...42ASi aws or gcp secrets Hashed admin password for authentication
DEPLOYEX_CONFIG_YAML_PATH /home/ubuntu/deployex.yaml system ENV Yaml configuration for Deployex and Monitored application
DEPLOYEX_OTP_TLS_CERT_PATH /usr/local/share/ca-certificates system ENV If using mTLS, the certificate PATH is needed

Once DeployEx runs, it fetches the configuration from the YAML file described in the path DEPLOYEX_CONFIG_YAML_PATH. The YAML file configuration contains the following fields:

account_name: "prod"                               # Deployex: Cloud/Environment Account name
hostname: "deployex.myphoenixapp.com"              # Deployex: hostname
port: 5001                                         # Deployex: port
release_adapter: "s3"                              # Deployex: release adapter type s3 or gcp-storage
release_bucket: "myphoenixapp-prod-distribution"   # Deployex: release distribution bucket name
secrets_adapter: "aws"                             # Deployex: release adapter type aws or gcp
secrets_path: "deployex-myphoenixapp-prod-secrets" # Deployex: secret path to be retrieved from
aws_region: "sa-east-1"                            # Deployex: aws region (only for AWS)
google_credentials: "/home/ubuntu/gcp-config.json" # Deployex: google credentials (only for GCP)
version: "0.4.0-rc1"                               # Deployex: Version
otp_version: 27                                    # Deployex: Otp version (It needs to match the monitored applications)
otp_tls_certificates: "/usr/local/share/ca-certificates" # Deployex (optional): Path to the certificates that will be consumed by Deployex
os_target: "ubuntu-24.04"                          # Deployex: Target OS server
deploy_rollback_timeout_ms: 600000                 # Deployex (optional, default: 600000): The maximum time allowed for attempting a deployment before considering the version as non-deployable and rolling back
deploy_schedule_interval_ms: 5000                  # Deployex (optional, default: 5000): Periodic checking for new deployments
metrics_retention_time_ms: 3600000                 # Deployex (optional, default: 3600000): Retention time for metrics
logs_retention_time_ms: 3600000                    # Deployex (optional, default: 3600000): Retention time for logs
monitoring:                                        # Deployex (optional, default: values described in memory): Monitoring features
  - type: "memory"
    enable_restart: true                           # Deployex (default: true): Restart app if memory usage exceeds 'restart_threshold_percent'
    warning_threshold_percent: 75                  # Deployex (default: 75): Issue a warning if memory usage exceeds this percent
    restart_threshold_percent: 95                  # Deployex (default: 95): Restart app if memory usage exceeds this percent
applications:
  - name: "myphoenixapp"                           # Application: Monitored app name (Elixir app name format)
    language: "elixir"                             # Application: App language (elixir, erlang or gleam)
    initial_port: 4000                             # Application: The initial port for starting the monitored app
    replicas: 2                                    # Application: Number of replicas
    env:                                           # Application (optional): Environment variables
      - key: MYPHOENIXAPP_PHX_HOST
        value: "myphoenixapp.com"
      - key: MYPHOENIXAPP_PHX_SERVER
        value: true
      - key: MYPHOENIXAPP_CLOUD_ENVIRONMENT
        value: "prod"
      - key: MYPHOENIXAPP_OTP_TLS_CERT_PATH
        value:  "/usr/local/share/ca-certificates"
      - key: MYPHOENIXAPP_SECRETS_ADAPTER
        value: "aws"
      - key: MYPHOENIXAPP_SECRETS_PATH
        value: "myphoenixapp-prod-secrets"
      - key: AWS_REGION
        value: "sa-east-1"
    monitoring:                                    # Application (optional, default: values described in atom, process and port): Monitoring features
      - type: "atom"
        enable_restart: true                       # Application (default: true): Restart app if memory usage exceeds 'restart_threshold_percent'
        warning_threshold_percent: 75              # Application (default: 75): Issue a warning if memory usage exceeds this percent
        restart_threshold_percent: 90              # Application (default: 90): Restart app if memory usage exceeds this percent
      - type: "process"
        enable_restart: true
        warning_threshold_percent: 75
        restart_threshold_percent: 90
      - type: "port"
        enable_restart: true
        warning_threshold_percent: 75
        restart_threshold_percent: 90
  - name: "myapp"                                 
    language: "elixir"                            
    initial_port: 4040                            
    replicas: 2                                   
    env:                                          
      - key: MYAPP_PHX_HOST
        value: "myapp.com"
      - key: MYAPP_PHX_SERVER
        value: true
      - key: MYAPP_CLOUD_ENVIRONMENT
        value: "prod"
      - key: MYAPP_OTP_TLS_CERT_PATH
        value:  "/usr/local/share/ca-certificates"
      - key: MYAPP_SECRETS_ADAPTER
        value: "aws"
      - key: MYAPP_SECRETS_PATH
        value: "myapp-prod-secrets"
      - key: AWS_REGION
        value: "sa-east-1"

For local testing, these variables are not expected or set to default values.

☁️ Cloud Providers

DeployEx offers a comprehensive set of Terraform examples for programmatically deploying in AWS and GCP, including detailed step-by-step setup instructions:

Installation

If you intend to install DeployEx directly on an Ubuntu server, you can utilize the installer script provided in the release package. For an example of monitored app, please see the setup for the Calori Web Server - AWS/Calori Web Server - GCP. The installer script requires a YAML configuration file, an example of which can be found here. This YAML file can also export environment variables specific to the monitored applications.

Currently, the release and installation process supports Ubuntu version 24.04. However, you have the option to manually compile and install DeployEx on your target system.

Pre-commands (Elixir only)

Your application will likely require database commands, such as migrations. DeployEx handles these through pre-commands specified in current.json under the pre_commands field. These commands will be executed in the order they are listed, before the application starts. If a pre-command is needed and does not require changes to the application itself, using pre-commands in conjunction with hotupgrade is ideal to avoid unnecessary downtime.

🔐 Secrets Requirements

DeployEx uses Secret Manager (AWS or GCP) to fetch its secrets via the config provider. The following environment variable configuration is expected for Secret Manager:

DEPLOYEX_SECRETS_ADAPTER=gcp
DEPLOYEX_SECRETS_PATH=deployex-myapp-prod-secrets

Within the secrets, the following key-value pairs are required:

ENV NAME EXAMPLE DESCRIPTION
DEPLOYEX_SECRET_KEY_BASE 42otsNl...Fpq3dIJ02 mix phx.gen.secret
DEPLOYEX_ERLANG_COOKIE my-cookie erlang cookie
DEPLOYEX_ADMIN_HASHED_PASSWORD $2b$1...5PAYTZjNQ42ASi Bcrypt.hash_pwd_salt("my-pass")

🏠 Running DeployEx locally

Recommended Supporting Repositories

For local testing and development, check out these complementary repositories:

🔨 Throubleshooting

Accessing DeployEx logs

# production
tail -f /var/log/deployex/deployex-stdout.log
tail -f /var/log/deployex/deployex-stderr.log
# local test
# not available when running as dev env

Connecting to the DeployEx IEX CLI

export RELEASE_COOKIE=cookie
# production
/opt/deployex/bin/deployex remote
# local test
# not available when running as dev env

Accessing monitored app logs

export monitored_app_name=myphoenixapp
export sname="${monitored_app_name}-2l0rkp"

# production
tail -f /var/log/${monitored_app_name}/${sname}-stdout.log
tail -f /var/log/${monitored_app_name}/${sname}-stderr.log
# local test
tail -f /tmp/deployex/varlog/${monitored_app_name}/${sname}-stdout.log
tail -f /tmp/deployex/varlog/${monitored_app_name}/${sname}-stderr.log

Connecting to the monitored app manually

Elixir

export monitored_app_name=myphoenixapp
export sname="${monitored_app_name}-v636fq"
export RELEASE_NODE=${sname}
export RELEASE_COOKIE=cookie
# production
/var/lib/deployex/service/${monitored_app_name}/${sname}/current/bin/${monitored_app_name} remote
# local test
/tmp/deployex/varlib/service/${monitored_app_name}/${sname}/current/bin/${monitored_app_name} remote

Gleam

export monitored_app_name=mygleamapp
export sname="${monitored_app_name}-v636fq"
export hostname=???? # From the local machine
export ssl_options="-proto_dist inet_tls -ssl_dist_optfile /tmp/inet_tls.conf" # If enabled
# production
erl -remsh ${sname}@${hostname} -setcookie cookie ${ssl_options}
# local test
erl -remsh ${sname}@${hostname} -setcookie cookie ${ssl_options}

Erlang

export monitored_app_name=myerlangapp
export sname="${monitored_app_name}-ly8tmo"
export RELX_REPLACE_OS_VARS=true
export RELEASE_NODE=${sname}
export RELEASE_COOKIE=cookie
export RELEASE_SSL_OPTIONS="-proto_dist inet_tls -ssl_dist_optfile /tmp/inet_tls.conf" # If enabled
# production
/var/lib/deployex/service/${monitored_app_name}/${sname}/current/bin/${monitored_app_name} remote_console
# local test
/tmp/deployex/varlib/service/${monitored_app_name}/${sname}/current/bin/${monitored_app_name} remote_console

❓How DeployEx handles services

DeployEx operates by monitoring applications and versions using folders and files, treating the monitored app as a service:

# test environment
/tmp/deployex/varlib/service/${monitored_app}/${sname}/previous/${monitored_app}
/tmp/deployex/varlib/service/${monitored_app}/${sname}/new/${monitored_app}
/tmp/deployex/varlib/service/${monitored_app}/${sname}/current/${monitored_app}
# production environment
/var/lib/deployex/service/${monitored_app}/${sname}/previous/${monitored_app}
/var/lib/deployex/service/${monitored_app}/${sname}/new/${monitored_app}
/var/lib/deployex/service/${monitored_app}/${sname}/current/${monitored_app}

🗨️ Getting involved

☎️ Contact us: Feel free to contact me on Linkedin.

©️ Copyright and License

Copyright (c) 2024, Thiago Esteves.

DeployEx source code is licensed under the MIT License.