{"id":13507803,"url":"https://github.com/rubencaro/bottler","last_synced_at":"2025-12-11T23:45:07.797Z","repository":{"id":24481107,"uuid":"27885313","full_name":"rubencaro/bottler","owner":"rubencaro","description":"Get your Elixir into proper recipients, and serve it nicely to final consumers","archived":false,"fork":false,"pushed_at":"2020-02-20T17:50:05.000Z","size":256,"stargazers_count":39,"open_issues_count":0,"forks_count":6,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-03-15T01:53:55.487Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/rubencaro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-12-11T18:23:50.000Z","updated_at":"2023-09-01T11:27:25.000Z","dependencies_parsed_at":"2022-08-22T22:10:24.970Z","dependency_job_id":null,"html_url":"https://github.com/rubencaro/bottler","commit_stats":null,"previous_names":["elpulgardelpanda/bottler"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubencaro%2Fbottler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubencaro%2Fbottler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubencaro%2Fbottler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubencaro%2Fbottler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rubencaro","download_url":"https://codeload.github.com/rubencaro/bottler/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246301963,"owners_count":20755512,"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":[],"created_at":"2024-08-01T02:00:39.455Z","updated_at":"2025-10-21T17:34:59.134Z","avatar_url":"https://github.com/rubencaro.png","language":"Elixir","funding_links":[],"categories":["Deployment"],"sub_categories":[],"readme":"# Bottler (BETA)\n\n[![Build Status](https://travis-ci.org/rubencaro/bottler.svg?branch=master)](https://travis-ci.org/rubencaro/bottler)\n[![Hex Version](http://img.shields.io/hexpm/v/bottler.svg?style=flat)](https://hex.pm/packages/bottler)\n[![Hex Version](http://img.shields.io/hexpm/dt/bottler.svg?style=flat)](https://hex.pm/packages/bottler)\n\n----\n\n# Abandoned !\nThis project has not been used by me in production for months and I don't expect to be able to dedicate time to it for a while. It was never meant to be more than my own tools, just in the open. So no big deal.\n\nAs always, feel free to fork it and go on with it if you find it useful for you!\n\n----\n\nBottler is a collection of tools that aims to help you generate releases, ship\nthem to your servers, install them there, and get them live on production.\n\n## What\n\nSeveral tools that can be used separately:\n\n* __release__: generate `tar.gz` files with your app and its dependencies (not\nincluding the whole `erts` by now).\n* __ship__: ship your generated `tar.gz` via `scp` to every server you configure.\n* __install__: properly install your shipped release on each of those servers.\n* __restart__: fire a quick restart to apply the newly installed release if you\nare using [Harakiri](http://github.com/rubencaro/harakiri).\n* __green_flag__: wait for the deployed application to signal it's working.\n* __deploy__: _release_, _ship_, _install_, _restart_, and then wait for _green_flag_.\n* __rollback__: quick _restart_ on a previous release.\n* __observer__: opens an observer window connected to given server.\n* __exec__: runs given command on every server, showing their outputs.\n* __goto__: opens an SSH session with a server on a new terminal window.\n\nYou should have public key ssh access to all servers you intend to work with.\nErlang runtime should be installed there too. Everything else, including Elixir\nitself, is included in the release.\n\nBy now it's not able to deal with all the hot code swap bolts, screws and nuts.\nSomeday will be.\n\n## Alternative to...\n\nInitially it was an alternative to [exrm](https://github.com/bitwalker/exrm), due to its lack of some features I love.\n\nRecently, after creating and using bottler on several projects for some months, I discovered [edeliver](https://github.com/boldpoker/edeliver) and it looks great! When I have time I will read carefully its code and play differences with bottler, maybe borrow some ideas.\n\nLooking forward to [distillery](https://github.com/bitwalker/distillery) too. The plan is to use it to generate the releases.\n\n## Use\n\nAdd to your `deps` like this:\n\n```elixir\n    {:bottler, \" \u003e= 0.5.0\"}\n```\n\nOr if you want to take a walk on the wild side:\n\n```elixir\n    {:bottler, github: \"rubencaro/bottler\"}\n```\n\nOn your config:\n\n```elixir\n    config :bottler, :params, [servers: [server1: [ip: \"1.1.1.1\"],\n                                         server2: [ip: \"1.1.1.2\"]],\n                               remote_user: \"produser\",\n                               rsa_pass_phrase: \"passphrase\",\n                               cookie: \"secretcookie\",\n                               max_processes: 262144,\n                               additional_folders: [\"docs\"],\n                               ship: [timeout: 60_000,\n                                      method: :scp],\n                               green_flag: [timeout: 30_000],\n                               goto: [terminal: \"terminator -T '\u003c%= title %\u003e' -e '\u003c%= command %\u003e'\"]\n                               forced_branch: \"master\",\n                               hooks: [pre_release: %{command: \"whatever\", continue_on_fail: false}]]\n```\n\n* `servers` - list of servers to deploy on.\n* `remote_user` - user name to log in.\n* `rsa_pass_phrase` - pass phrase for your SSH keys (We recommend not to put it on plain text here. `System.get_env(\"RSA_PASS_PHRASE\")` would do.).\n* `cookie` - distributed Erlang cookie.\n* `max_processes` - maximum number of processes allowed on ErlangVM ([see here](http://erlang.org/doc/man/erl.html#max_processes)). Defaults to `262144`.\n* `additional_folders` - additional folders to include in the release under\n   the `lib` folder.\n* `ship` - options for the `ship` task\n  * `timeout` - timeout millis for shipment through scp, defaults to 60_000\n  * `method` - method of shipment, one of (`:scp`, `:remote_scp`, etc..)\n* `green_flag` - options for the `green_flag` task\n  * `timeout` - timeout millis waiting for green flags, defaults to 30_000\n* `goto` - options for the `goto` task\n  * `terminal` - template for the actual terminal command\n* `forced_branch` - only allow executing _dangerous_ tasks when local git is on given branch\n* `hooks` - hooks to run external commands on interesting moments\n\nThen you can use the tasks like `mix bottler.release`. Take a look at the docs for each task with `mix help \u003ctask\u003e`.\n\n`prod` environment is used by default. Use like `MIX_ENV=other_env mix bottler.taskname` to force it to `other_env`.\n\nYou may also want to add `\u003cproject\u003e/rel` and `\u003cproject\u003e/.bottler` to your `.gitignore` if you don't want every generated file, including release `.tar.gz`, get into your repo.\n\n## Release\n\nBuild a release file. Use like `mix bottler.release`.\n\nAny script (or `EEx` template) on a `lib/scripts folder` will be included into the release package. The `install` task also links that folder directly from the current release, so you can see your scripts on production inside `$HOME/\u003cproject\u003e/current/scripts`. The contents of the folder will be merged with the own `bottler` `lib/scripts` folder. Take a look at it for examples ( https://github.com/rubencaro/bottler/tree/master/lib/scripts ).\n\n## Ship\n\nShip a release file to configured remote servers.\nUse like `mix bottler.ship`.\n\nYou can configure some things about it, under the _ship_ section:\n* __timeout__: The timeout that applies to the upload process.\n* __method__: One of:\n  * __scp__: Straight _scp_ from the local machine to every target server.\n  * __remote_scp__: Upload the release only once from your local machine to the first configured server, and then _scp_ remotely to every other target.\n\n## Install\n\nInstall a shipped file on configured remote servers.\nUse like `mix bottler.install`.\n\n## Restart\n\nTouch `tmp/restart` on configured remote servers.\nThat expects to have `Harakiri` or similar software reacting to that.\nUse like `mix bottler.restart`.\n\n## Alive Loop\n\nTipically implemented on production like this:\n\n```elixir\n@doc \"\"\"\nTell the world outside we are alive\n\"\"\"\ndef alive_loop(opts \\\\ []) do\n  # register the name if asked\n  if opts[:name], do: Process.register(self,opts[:name])\n\n  :timer.sleep 5_000\n  tmp_path = Application.get_env(:myapp, :tmp_path) |\u003e Path.expand\n  {_, _, version} = Application.started_applications |\u003e Enum.find(\u0026(match?({:myapp, _, _}, \u00261)))\n  :os.cmd 'echo \\'#{version}\\' \u003e #{tmp_path}/alive'\n  alive_loop\nend\n```\n\nAnd run by a `Task` on the supervision tree like this:\n\n```elixir\nworker(Task, [MyApp, :alive_loop, [[name: MyApp.AliveLoop]]])\n```\n\nIt touches the `tmp/alive` file every ~5 seconds, so anyone outside of the ErlangVM can tell if the app is actually running.\n\n### Watchdog script for crontab\n\nAmong the generated scripts, put by the _deploy_ task inside `$HOME/\u003cproject\u003e/current/scripts`, there's a `watchdog.sh` meant to be run by `cron`.\n\nThat script checks the _mtime_ of the `tmp/alive` file to ensure that it's younger than 60 seconds. If it's not, then it starts the application. If the application is running, the watchdog script will not even try to start it again.\n\n### Green Flag Test\n\nA task to wait until the contents of `tmp/alive` file matches the version of the `current` release, or the given timeout is reached.\n\nUse like `mix bottler.green_flag`.\n\nIf you have special needs with the start of your application, such as to wait for some cache to fill or some connections to be made, then you just have to control the actual value that is written on the `alive` file. __It has to match the new version only when everything is ready to work.__ You can use an `Agent` like:\n\n```elixir\n@doc \"\"\"\nTell the world outside we are alive\n\"\"\"\ndef alive_loop(opts \\\\ []) do\n  #...\n  version = Agent.get(:version_holder, \u0026(\u00261))\n  #...\nend\n```\n\n## Deploy\n\nBuild a release file, ship it to remote servers, install it, and restart\nthe app. Then it waits for the green flag test. No hot code swap for now.\n\nUse like `mix deploy`.\n\n## Rollback\n\nSimply move the _current_ link to the previous release and restart to\napply. It's also possible to deploy a previous release, but this is\nquite faster.\n\nBe careful because the _previous release_ may be different on each server.\nIt's up to you to keep all your servers rollback-able (yeah).\n\nUse like `mix bottler.rollback`.\n\n## Observer\n\nUse like `mix observer server1`\n\nIt takes the ip of the given server from configuration, then opens a double SSH tunnel with its epmd service and its application node. Then executes an elixir script which spawns an observer window locally, connected with the tunnelled node. You just need to select the remote node from the _Nodes_ menu.\n\n## Exec\n\nUse like `mix bottler.exec 'ls -alt some/path'`\n\nIt runs the given command through parallel SSH connections with all the configured servers. It accepts an optional _--timeout_ parameter.\n\n## Goto\n\nUse like `mix goto server1`\n\nIt opens an SSH session on a new terminal window on the server with given name. The actual `terminal` command can be configured as a template.\n\n## GCE support\n\nWhenever you can use Google's `gcloud` from your computer (i.e. authenticate and see if it works), you can configure `bottler` to use it too to get your instances IP addresses. Instead of:\n\n```elixir\n    servers: [server1: [ip: \"1.1.1.1\"],\n              server2: [ip: \"1.1.1.2\"]]\n```\n\nYou just do:\n```elixir\n    servers: [gce_project: \"project-id\", match: \"regexstr\"]\n```\nWhen you perform an operation on a server, its ip will be obtained using `gcloud` command. You don't need to reserve more static IP addresses for your instances.\n\nOptionally you can give a `match` regex string to default filter server names given by gcloud. Just the same you would give to the `--servers` switch of the tasks. This filter will be added to the one given at the commandline switch. I.e. if you configure `match` and then pass `--servers`, then only servers with a name that matches both regexes will pass.\n\n## Hooks\n\nYou can configure hooks to be run at several points of the process. To define a hook you must add it to your configuration like this:\n\n```elixir\nhooks: [hook_point_name: %{command: \"whatever\", continue_on_fail: false}],\n```\n\n`continue_on_fail` marks the behaviour of bottler when the return code of given command is not zero. When `continue_on_fail` is `true`, bottler will continue with the normal execution. Otherwise it will halt.\n\nSupported hook points are:\n* __pre_release__: executed right before the _release_ task\n\n## TODOs\n\n* Use [distillery](https://github.com/bitwalker/distillery)\n* Add more testing\n* Separate section for documenting every configuration option\n* Get it stable on production\n* Complete README\n* Rollback to _any_ previous version\n* Add support for deploy to AWS instances [*](https://github.com/gleber/erlcloud)[*](notes/aws.md)\n\n## Changelog\n\n### master\n\n* Add pre-release hook\n* Support for hooks\n* Remove 1.4 warnings\n* Configurable `max_processes`\n* Log using server names\n* Fix some `scp` glitches when shipping between servers\n* Support for `Regex` on server names\n* Green flag support.\n* Support for forced release branch\n* Log guessed server ips\n* Options to filter target servers from command line\n* Resolve server ips only once\n* Add support for deploy to GCE instances\n* remove `helper_scripts` task\n* `goto` task\n* Use SSHEx 2.1.0\n* Cookie support\n* configurable shipment timeout\n* `erl_connect` (no Elixir needed on target)\n* `observer` task\n* `bottler.exec` task\n* `remote_scp` shipment support\n* log erts versions on both sides\n\n### 0.5.0\n\n* Use new SSHEx 1.1.0\n\n### 0.4.1\n\n* Fix `:ssh` sometimes not started on install.\n\n### 0.4.0\n\n* Use [SSHEx](https://github.com/rubencaro/sshex)\n* Add __helper_scripts__\n\n### 0.3.0\n\n* Individual tasks for each step\n* Add connect script\n* Add fast rollback\n* Few README improvements\n\n### 0.2.0\n\n* First package released\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frubencaro%2Fbottler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frubencaro%2Fbottler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frubencaro%2Fbottler/lists"}