{"id":25119279,"url":"https://github.com/thiagoesteves/deployex","last_synced_at":"2025-04-02T13:26:14.835Z","repository":{"id":239238654,"uuid":"796812023","full_name":"thiagoesteves/deployex","owner":"thiagoesteves","description":"The ideal project to supervise your Elixir application","archived":false,"fork":false,"pushed_at":"2024-05-22T22:11:15.000Z","size":423,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-05-22T23:15:17.554Z","etag":null,"topics":["deployment","deployment-automation","elixir-develoment-environment","elixir-otp","erlang-distribution","erlang-otp","nginx","nginx-proxy","non-helm","non-k8s"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thiagoesteves.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"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}},"created_at":"2024-05-06T17:15:43.000Z","updated_at":"2024-06-24T20:40:14.639Z","dependencies_parsed_at":"2024-05-28T20:03:46.173Z","dependency_job_id":"febc8f07-80b2-4b20-9a1d-461d39c0790a","html_url":"https://github.com/thiagoesteves/deployex","commit_stats":null,"previous_names":["thiagoesteves/deployex"],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thiagoesteves%2Fdeployex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thiagoesteves%2Fdeployex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thiagoesteves%2Fdeployex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thiagoesteves%2Fdeployex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thiagoesteves","download_url":"https://codeload.github.com/thiagoesteves/deployex/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246821690,"owners_count":20839472,"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","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":["deployment","deployment-automation","elixir-develoment-environment","elixir-otp","erlang-distribution","erlang-otp","nginx","nginx-proxy","non-helm","non-k8s"],"created_at":"2025-02-08T04:38:39.254Z","updated_at":"2025-04-02T13:26:14.828Z","avatar_url":"https://github.com/thiagoesteves.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DeployEx\n\n\u003e Who supervises the supervisor (of your application)?\n\n![Development](https://img.shields.io/badge/STATUS-Development_v0.3.0-blue) [![Build Status](https://github.com/thiagoesteves/deployex/workflows/Deployex%20CI/badge.svg)](https://github.com/thiagoesteves/deployex/actions/workflows/pr-ci.yml) \n\nDeployEx 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.\n\nDeployEx 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.\n\nTo 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](https://www.youtube.com/watch?v=MV4ROe6xmlI) at CODE BEAM America 2025.\n\nDeployEx is currently used by:\n * [Calori Web Server](https://github.com/thiagoesteves/calori) for __Elixir__ applications and you can check it at [homepage](https://calori.com.br).\n * [Cochito Web Server](https://github.com/chouzar/cochito) for __Gleam__ applications.\n * [Snake Game with Cowboy](https://github.com/thiagoesteves/erlgame) for __Erlang__ applications.\n\n![Deployment Architecture](docs/static/deployex.png)\n\nUpon 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.\n\n[![Watch the video](docs/static/deployex_monitoring_app_tls.png)](https://youtu.be/tzWcCmuqNV4)\n\nhttps://www.youtube.com/watch?v=MV4ROe6xmlI\n\n## 🔉 Features\n\n * Monitors multiple Beam application instances (Elixir/Erlang/Gleam) and automatically restarts them if they crash for any reason.\n * Includes a backoff delay restart mechanism to prevent excessive restarts.\n * Performs full deployments based solely on the release files generated by:\n   - `mix release` for Elixir.\n   - `gleam export` for Gleam.\n   - `rebar3 as prod tar` for Erlang.\n * Supports hot code reloading for:\n   - Elixir applications using the [Jellyfish](https://github.com/thiagoesteves/jellyfish) library.\n   - Erlang applications using the [rebar3_appup_plugin](https://github.com/lrascao/rebar3_appup_plugin) plugin.\n * Supports the following cloud providers:\n   - Amazon Web Services (AWS)\n   - Google Cloud Provisioning (GCP)\n * Provides rollback functionality if a monitored app version remains unstable for 10 minutes.\n * Rolled-back monitored app versions are ghosted, preventing their redeployment.\n * Ensures all instances remain connected to the OTP distribution, including DeployEx itself.\n * Supports OTP distribution with mutual TLS (mTLS) for secure monitoring of apps and DeployEx.\n * Provides the ability to run pre-commans prior deployments for Database migrations or any other eval command.\n * Provides a friendly UI that only authenticated users can access.\n * Allows setting a previously configured version in the UI, enabling DeployEx to enforce deployment of a specific version.\n * Supports individual application restarts via the UI, including DeployEx itself.\n * Provides easy access to the application shell:\n   - IEx shell for monitored Elixir apps and DeployEx.\n   - Erlang shell for monitored Gleam/Erlang apps.\n * Supports access to live log files (stdout and stderr) for both monitored apps and DeployEx.\n * Supports observability for all connected applications via [Observer Web](https://github.com/thiagoesteves/observer_web).\n * Supports safe tracing for all connected applications.\n * Provides visualization of Host System memory and CPU usage.\n * Provides easy access to the host shell (tmux).\n * Provides installer script to be used with ubuntu hosts.\n * Provides status information per instance:\n   - OTP connectivity\n   - Version history\n   - Last deployment status\n   - Number of crash restarts\n   - Number of forced restarts\n * And much more ...\n\n\u003e [!NOTE]\n\u003e 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.\n\n## ⚠️ Next steps\n\n### What is coming next\n\n- [ ] 💤 Integrate CPU utilization monitoring from the OTP distribution.\n- [ ] 💤 Support for shutting down applications before run out of memory.\n- [ ] 💤 Handle different apps within the same DeployEx instances.\n- [ ] 💤 Lazy deployments for Phoenix apps (Delay Endpoint start to allow fast switch for full deployments)\n- [ ] 💤 Continuous improvement in UI design.\n- [ ] 💤 Health checks via OTP distribution\n- [ ] 💤 Secrets from Environment variables to allow installation on servers with no cloud provider\n- [ ] 💤 Orchestrate distributed databasses like [Khepri](https://github.com/rabbitmq/khepri)\n- [ ] 💤 Enhance installer to become an elixir app capable of hotupgrade DeployEx\n\n## 📁 Getting Started\n\n\u003e [!WARNING]\n\u003e Since OTP distribution is heavily used between the DeployEx and Monitored Applications, users must ensure that both applications are running the same OTP version to prevent malfunctions.\n\n| DeployEx version   |      Default OTP version      | \n|----------|-------------|\n| __0.3.0__ | __26.2.5.6__ |\n| __0.3.1__ | __26.2.5.6__ |\n\n### Running the application\n\nYou can kickstart the setup with the following commands, the default number of replicas is 3:\n```bash\nmix deps.get\niex --sname deployex --cookie cookie -S mix phx.server\n[info] Initialising deployment server\n[info] Running DeployexWeb.Endpoint with Bandit 1.5.7 at 127.0.0.1:5001 (http)\n[info] Access DeployexWeb.Endpoint at http://localhost:5001\n[watch] build finished, watching for changes...\nErlang/OTP 26 [erts-14.1.1] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit]\n\nInteractive Elixir (1.16.0) - press Ctrl+C to exit (type h() ENTER for help)\n\nRebuilding...\n\nDone in 390ms.\n[error] Invalid version map at: /tmp/myphoenixapp/versions/myphoenixapp/local/current.json reason: enoent\n```\n\nNow you can visit [`localhost:5001`](http://localhost:5001) from your browser and enter the credentials for the admin user, *__username: admin password: deployex__*. You should expect the following dashboard:\n\n![Empty Dashboard](docs/static/deployex_no_monitoring_app.png)\n\n\u003e [!NOTE]\n\u003e 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](https://github.com/thiagoesteves/deployex/tree/thi/add-erlang-hot-upgrade-support?tab=readme-ov-file#-running-deployex-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\n\n### How DeployEx handles monitored application Version/Release\n\nThe 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.\n\n#### Version file (current.json) \n\nExpected location in the release folder:\n```bash\n# production path\n./{bucket}/versions/{monitored_app}/{env}/current.json\n# local test path\n/tmp/{monitored_app}/versions/{monitored_app}/{env}/current.json\n```\n\nExpected JSON format for `current.json`:\n```bash\n{\n  \"version\": \"1.0.0\",\n  \"pre_commands\": [ \"eval MyApp.Migrator.create\", \"eval MyApp.Migrator.migrate\" ], # optional field\n  \"hash\": \"local\"\n}\n```\n\nOnce 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.\n\n#### Release package\n\nExpected location in the release folder:\n```bash\n# production path\n./{bucket}/dist/{monitored_app}/{monitored_app}-{version}.tar.gz\n# local test path\n/tmp/{monitored_app}/dist/{monitored_app}/{monitored_app}-{version}.tar.gz\n```\n## 🛠️ Production Information\n\n### Environment Variables\n\nDeployEx application typically requires several environment variables to be defined for proper operation. Ensure that you have the following environment variables set when running in production where the ones that have a default value available are not required:\n\n| ENV NAME   |      EXAMPLE      |  SOURCE |  DEFAULT | DESCRIPTION |\n|----------|-------------|------:|------|------|\n| __DEPLOYEX_SECRET_KEY_BASE__ | 42otsNl...Fpq3dIJ02 | aws or gcp secrets | -/- | secret key used for encryption |\n| __DEPLOYEX_ERLANG_COOKIE__ | cookie | aws or gcp secrets | -/- | erlang cookie |\n| __DEPLOYEX_ADMIN_HASHED_PASSWORD__ | $2b$1...5PAYTZjNQ42ASi | aws or gcp secrets | -/- | Hashed admin password for authentication |\n| __DEPLOYEX_MONITORED_APP_NAME__ | myphoenixapp | system ENV | -/- | Monitored app name |\n| __DEPLOYEX_MONITORED_APP_LANG__ | __elixir__, __gleam__ or __erlang__  | system ENV | -/- |  Monitored app language |\n| __DEPLOYEX_CLOUD_ENVIRONMENT__ | prod | system ENV | -/- | cloud env name |\n| __AWS_REGION__ | us-east2 | system ENV | -/- | the aws region |\n| __GOOGLE_APPLICATION_CREDENTIALS__ | /path/to/file.json | system ENV | -/- | the google application credentials path |\n| __DEPLOYEX_PHX_HOST__ | example.com | system ENV | -/- | The hostname for your application |\n| __DEPLOYEX_PHX_PORT__ | 5001 | system ENV | 5001 | The port on which the application will run |\n| __DEPLOYEX_PHX_SERVER__ | true | system ENV | true | enable/disable server |\n| __DEPLOYEX_RELEASE_ADAPTER__ | __s3__ or __gcp-storage__ | system ENV | -/- | release adapter type |\n| __DEPLOYEX_RELEASE_BUCKET__ | myphoenixapp-prod-distribution | system ENV | -/- | release distribution bucket name |\n| __DEPLOYEX_SECRETS_ADAPTER__ | __aws__ or __gcp__ | system ENV | -/- | release adapter type |\n| __DEPLOYEX_SECRETS_PATH__ | deployex-myphoenixapp-prod-secrets | system ENV | -/- | secret path to be retrieved from |\n| __DEPLOYEX_MONITORED_APP_PORT__ | 4000 | system ENV | 4000 | the initial port for starting the monitored apps |\n| __DEPLOYEX_MONITORED_REPLICAS__ | 2 | system ENV | 3 | Number of replicas to monitor |\n| __DEPLOYEX_DEPLOY_TIMEOUT_ROLLBACK_MS__ | 600000 | system ENV | 600000 | The maximum time allowed for attempting a deployment before considering the version as non-deployable and rolling back |\n| __DEPLOYEX_DEPLOY_SCHEDULE_INTERVAL_MS__ | 5000 | system ENV | 5000 | Periodic checking for new deployments |\n\nFor local testing, these variables are not expected or set to default values.\n\n### ☁️ Cloud Providers\n\nDeployEx offers a comprehensive set of Terraform examples for programmatically deploying in AWS and GCP, including detailed step-by-step setup instructions:\n\n * [AWS deployment for Elixir](docs/examples/aws-elixir/README.md)\n * [GCP deployment for Elixir](docs/examples/gcp-elixir/README.md)\n * [AWS deployment for Gleam](docs/examples/aws-gleam/README.md)\n\n### Installation\n\nIf you intend to install DeployEx directly on an Ubuntu server, you can utilize the [installer script](/devops/installer/deployex.sh) provided in the release package. For an example of monitored app, please see the setup for the [Calori Web Server - AWS](https://github.com/thiagoesteves/calori/blob/main/devops/aws/terraform/modules/standard-account/cloud-config.tpl)/[Calori Web Server - GCP](https://github.com/thiagoesteves/calori/blob/main/devops/gcp/terraform/modules/standard-account/cloud-config.tpl). The installer script requires a JSON configuration file, an example of which can be found [here](/devops/installer/deployex-config.json). This JSON file can also export environment variables specific to the monitored applications.\n\nCurrently, the release and installation process supports Ubuntu versions 20.04 and 22.04. However, you have the option to manually compile and install DeployEx on your target system.\n\n### Pre-commands (Elixir only)\n\nYour 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.\n\n### 🔐 Secrets Requirements\n\nDeployEx uses Secret Manager (AWS or GCP) to fetch its secrets via the config provider. The following environment variable configuration is expected for Secret Manager:\n\n```bash\nDEPLOYEX_SECRETS_ADAPTER=gcp\nDEPLOYEX_SECRETS_PATH=deployex-myapp-prod-secrets\n```\n\nWithin the secrets, the following key-value pairs are required:\n| ENV NAME | EXAMPLE | DESCRIPTION |\n|----------|-------------|------|\n| __DEPLOYEX_SECRET_KEY_BASE__ | 42otsNl...Fpq3dIJ02 | mix phx.gen.secret |\n| __DEPLOYEX_ERLANG_COOKIE__ | my-cookie | erlang cookie |\n| __DEPLOYEX_ADMIN_HASHED_PASSWORD__ | $2b$1...5PAYTZjNQ42ASi | Bcrypt.hash_pwd_salt(\"my-pass\") |\n\n## 🏠 Running DeployEx locally\n\n * [Elixir Applications](docs/examples/local-elixir/README.md)\n * [Gleam Applications](docs/examples/local-gleam/README.md)\n * [Erlang Applications](docs/examples/local-erlang/README.md)\n\n\n## 🔨 Throubleshooting\n\n### Accessing DeployEx logs\n\n```bash\n# production\ntail -f /var/log/deployex/deployex-stdout.log\ntail -f /var/log/deployex/deployex-stderr.log\n# local test\n# not available when running as dev env\n```\n\n### Connecting to the DeployEx IEX CLI\n\n```bash\nexport RELEASE_NODE_SUFFIX=\"\"\nexport RELEASE_COOKIE=cookie\n# production\n/opt/deployex/bin/deployex remote\n# local test\n# not available when running as dev env\n```\n\n### Accessing monitored app logs\n\n```bash\nexport instance=1\nexport monitored_app_name=myphoenixapp\n# production\ntail -f /var/log/${monitored_app_name}/${monitored_app_name}-${instance}-stdout.log\ntail -f /var/log/${monitored_app_name}/${monitored_app_name}-${instance}-stderr.log\n# local test\ntail -f /tmp/${monitored_app_name}/${monitored_app_name}/${monitored_app_name}-${instance}-stdout.log\ntail -f /tmp/${monitored_app_name}/${monitored_app_name}/${monitored_app_name}-${instance}-stderr.log\n```\n\n### Connecting to the monitored app manually\n\n#### Elixir\n\n```bash\nexport instance=1\nexport monitored_app_name=myphoenixapp\nexport RELEASE_NODE_SUFFIX=-${instance}\nexport RELEASE_COOKIE=cookie\n# production\n/var/lib/deployex/service/${monitored_app_name}/${instance}/current/bin/${monitored_app_name} remote\n# local test\n/tmp/deployex/varlib/service/${monitored_app_name}/${instance}/current/bin/${monitored_app_name} remote\n```\n\n#### Gleam\n\n```bash\nexport instance=1\nexport monitored_app_name=mygleamapp\nexport hostname=??? # From the local machine\nexport ssl_options=\"-proto_dist inet_tls -ssl_dist_optfile /tmp/inet_tls.conf\" # If enabled\n# production\nerl -remsh ${monitored_app_name}-${instance}@${hostname} -setcookie cookie ${ssl_options}\n# local test\nerl -remsh ${monitored_app_name}-${instance}@${hostname} -setcookie cookie ${ssl_options}\n```\n\n#### Erlang\n\n```bash\nexport instance=1\nexport monitored_app_name=myerlangapp\nexport RELX_REPLACE_OS_VARS=true\nexport RELEASE_NODE=${monitored_app_name}-${instance}\nexport RELEASE_COOKIE=cookie\nexport RELEASE_SSL_OPTIONS=\"-proto_dist inet_tls -ssl_dist_optfile /tmp/inet_tls.conf\" # If enabled\n# production\n/var/lib/deployex/service/${monitored_app_name}/${instance}/current/bin/${monitored_app_name} remote_console\n# local test\n/tmp/deployex/varlib/service/${monitored_app_name}/${instance}/current/bin/${monitored_app_name} remote_console\n```\n\n## ❓How DeployEx handles services\n\nDeployEx operates by monitoring applications and versions using folders and files, treating the monitored app as a service:\n```bash\n# test environment\n/tmp/deployex/varlib/service/${monitored_app}/${instance}/previous/${monitored_app}\n/tmp/deployex/varlib/service/${monitored_app}/${instance}/new/${monitored_app}\n/tmp/deployex/varlib/service/${monitored_app}/${instance}/current/${monitored_app}\n```\n\n```bash\n# production environment\n/var/lib/deployex/service/${monitored_app}/${instance}/previous/${monitored_app}\n/var/lib/deployex/service/${monitored_app}/${instance}/new/${monitored_app}\n/var/lib/deployex/service/${monitored_app}/${instance}/current/${monitored_app}\n```\n\nThe deployment process involves several steps to ensure smooth transitions:\n\n### Full deployment\n\n1. *__Download and Unpack the New Version:__*\n The new version of the application is downloaded and unpacked into the `new` service folder, ready for deployment.\n2. *__Check if the release contain a hot-upgrade or full deployment:__*\n DeployEx will check the release file received and if it is a full deployment, goes to the step 3 .\n3. *__Stop the Current Application:__*\nThe currently running application instance is stopped to prepare for the new deployment.\n4. *__Delete the Previous Service Folder:__*\n The `previous` service folder, containing the previous version of the application, is deleted to make space for the new version.\n5. *__Move the Current Service:__*\n The `current` service folder, representing the current version of the application, is moved to the `previous` service folder. Simultaneously, the `new` service folder is moved to become the new `current` service folder.\n6. *__Run pre_commands:__*\n Before starting the application, DeployEx will attempt to run any pre_commands (if set) using the `current` service folder.\n7. *__Start the Application:__*\n Finally, the application is started using the version now residing in the `current` service folder, ensuring that the latest version is active and operational.\n\nBy following this process, DeployEx facilitates deployments, ensuring that applications are updated while minimizing downtime.\n\n### Hot-upgrades\n\nFor this scenario, there will be no moving files/folders since the target is to keep the current service folder updated. The sequence is:\n\n1. *__Download and Unpack the New Version:__*\n The new version of the application is downloaded and unpacked into the `new` service folder, ready for deployment.\n2. *__Check if the release contain a hot-upgrade or full deployment:__*\n DeployEx will check the release file received and if it is a hot-upgrade, goes to the step 3 .\n3. *__Run pre_commands:__*\n Before executing the hot-upgrade, DeployEx will attempt to run any pre_commands (if set) using the `new` service folder.\n4. *__Execute the Hotupgrade checks and verification__*\n DeployEx will try to run the hotupgrade sequence and if succeeds, it makes the changes permanent. In any case of failure, it tries to execute a full deployment with the same release file.\n\n## 🗨️ Getting involved\n\n☎️ **Contact us:**\nFeel free to contact me on [Linkedin](https://www.linkedin.com/in/thiago-cesar-calori-esteves-972368115/).\n\n## ©️ Copyright and License\n\nCopyright (c) 2024, Thiago Esteves.\n\nDeployEx source code is licensed under the [MIT License](LICENSE.md).\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthiagoesteves%2Fdeployex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthiagoesteves%2Fdeployex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthiagoesteves%2Fdeployex/lists"}