Getting Started with bc_gitops
View SourceThis guide walks you through setting up bc_gitops to manage OTP applications using GitOps principles.
What is GitOps?
GitOps is an operational framework where:
- Git is the source of truth - The desired state of your system is stored in a Git repository
- Declarative configuration - You describe what you want, not how to achieve it
- Automatic reconciliation - The system continuously compares desired vs actual state and takes corrective actions
bc_gitops brings this pattern to the BEAM ecosystem, allowing you to manage OTP applications the same way Flux or ArgoCD manage Kubernetes workloads.
Prerequisites
- Erlang/OTP 25+ or Elixir 1.14+
- Git installed and accessible in PATH
- A Git repository for storing application specifications
Installation
For Erlang (rebar3)
Add to your rebar.config:
{deps, [
{bc_gitops, "0.4.0"}
]}.For Elixir (Mix)
Add to your mix.exs:
def deps do
[
{:bc_gitops, "~> 0.4.0"}
]
endStep 1: Create Your GitOps Repository
Create a new Git repository to store your application specifications:
mkdir my-gitops-repo
cd my-gitops-repo
git init
mkdir apps
Step 2: Define an Application
Create a specification file for each application you want to manage. Let's create one for a hypothetical my_web_app:
mkdir apps/my_web_app
bc_gitops supports three configuration formats. Choose the one you prefer:
Option A: Erlang Terms (app.config)
Create apps/my_web_app/app.config:
#{
%% Application name (must match the OTP application name)
name => my_web_app,
%% Version to deploy
version => <<"1.0.0">>,
%% Where to get the application from
source => #{
type => hex %% From hex.pm
%% Or for git:
%% type => git,
%% url => <<"https://github.com/myorg/my_web_app.git">>,
%% ref => <<"v1.0.0">>
},
%% Application environment (passed to application:set_env)
env => #{
port => 8080,
pool_size => 10
},
%% Health check configuration (optional)
health => #{
type => http,
port => 8080,
path => <<"/health">>,
interval => 30000, %% Check every 30 seconds
timeout => 5000 %% Timeout after 5 seconds
},
%% Dependencies (other managed apps that must start first)
depends_on => []
}.Option B: YAML (app.yaml)
Note: Requires
yamerldependency. Add{yamerl, "0.10.0"}to your deps.
Create apps/my_web_app/app.yaml:
name: my_web_app
version: "1.0.0"
source:
type: hex
# Or for git:
# type: git
# url: https://github.com/myorg/my_web_app.git
# ref: v1.0.0
env:
port: 8080
pool_size: 10
health:
type: http
port: 8080
path: /health
interval: 30000
timeout: 5000
depends_on: []Option C: JSON (app.json)
Note: Requires OTP 27+ for native JSON support. For older versions, add
jsxorjiffyto your deps.
Create apps/my_web_app/app.json:
{
"name": "my_web_app",
"version": "1.0.0",
"source": {
"type": "hex"
},
"env": {
"port": 8080,
"pool_size": 10
},
"health": {
"type": "http",
"port": 8080,
"path": "/health",
"interval": 30000,
"timeout": 5000
},
"depends_on": []
}Config File Priority
bc_gitops looks for config files in this order:
app.config(Erlang terms)app.yaml/app.yml(YAML)app.json(JSON)config.yaml/config.yml/config.json/config
Commit and push:
git add .
git commit -m "Add my_web_app specification"
git remote add origin https://github.com/myorg/my-gitops-repo.git
git push -u origin main
Step 3: Choose a Runtime Module
bc_gitops needs to know how to deploy applications. As of v0.3.0, the built-in bc_gitops_runtime_default is fully functional:
- Fetches packages from hex.pm (Erlang via rebar3, Elixir via mix)
- Clones git repositories and compiles them
- Clean restarts on version upgrades (ensures routes, supervisors, and app metadata are fresh)
- Code path management - automatically adds compiled modules to the VM
Note: Hot code reload is available via
bc_gitops_hot_reloadmodule for same-version code changes (e.g., branch tracking during development).
Why Restart on Upgrade?
Version upgrades restart the application rather than hot-reloading because several things in OTP cannot be updated at runtime:
Application metadata -
application:get_key/2reads from a cache populated at app start. Hot reload doesn't refresh this cache, sovsn,description, and custom keys return stale values.Cowboy/HTTP routes - Routes are compiled into the dispatch table when
cowboy:start_clear/3is called. New routes added in an upgrade won't be registered without restarting the listener.Supervision trees - New child specs, changed restart strategies, or restructured supervisors require the supervisor to restart.
Application environment - While
application:set_env/3can update values, many applications read config only at startup.
For same-version code changes (e.g., tracking a master branch during development), hot reload works well because you're only updating module bytecode, not structural changes.
For most use cases, the default runtime works out of the box. For custom requirements, implement the bc_gitops_runtime behaviour.
Create src/my_app_runtime.erl:
-module(my_app_runtime).
-behaviour(bc_gitops_runtime).
-include_lib("bc_gitops/include/bc_gitops.hrl").
-export([deploy/1, remove/1, upgrade/2, reconfigure/1, get_current_state/0]).
%% @doc Deploy a new application
deploy(#app_spec{name = Name, version = Version, env = Env}) ->
%% 1. Download/fetch the application (if needed)
%% 2. Set environment variables
set_env(Name, Env),
%% 3. Start the application
case application:ensure_all_started(Name) of
{ok, _} ->
{ok, #app_state{
name = Name,
version = Version,
status = running,
started_at = calendar:universal_time(),
health = unknown,
env = Env
}};
{error, Reason} ->
{error, {start_failed, Reason}}
end.
%% @doc Remove (stop) an application
remove(Name) ->
case application:stop(Name) of
ok -> ok;
{error, {not_started, _}} -> ok;
{error, Reason} -> {error, Reason}
end.
%% @doc Upgrade an application to a new version
upgrade(AppSpec, _OldVersion) ->
%% Simple strategy: stop and redeploy
%% For zero-downtime, implement hot code upgrades
remove(AppSpec#app_spec.name),
deploy(AppSpec).
%% @doc Update application configuration without restart
reconfigure(#app_spec{name = Name, version = Version, env = NewEnv}) ->
set_env(Name, NewEnv),
{ok, #app_state{
name = Name,
version = Version,
status = running,
started_at = calendar:universal_time(),
health = unknown,
env = NewEnv
}}.
%% @doc Get the current state of all managed applications
get_current_state() ->
%% Query your application registry/supervisor
{ok, #{}}.
%% Internal helper
set_env(App, Env) ->
maps:foreach(fun(K, V) ->
application:set_env(App, K, V)
end, Env).Step 4: Configure bc_gitops
Add configuration to your sys.config (or config/config.exs for Elixir):
Erlang (sys.config)
[
{bc_gitops, [
%% Required: Git repository URL
{repo_url, "https://github.com/myorg/my-gitops-repo.git"},
%% Optional: Local clone path (default: /var/lib/bc_gitops)
{local_path, "/var/lib/bc_gitops"},
%% Optional: Branch to track (default: main)
{branch, "main"},
%% Optional: Reconcile interval in ms (default: 60000)
{reconcile_interval, 60000},
%% Optional: Directory containing app specs (default: apps)
{apps_dir, "apps"},
%% Required: Your runtime implementation
{runtime_module, my_app_runtime}
]}
].Elixir (config.exs)
config :bc_gitops,
repo_url: "https://github.com/myorg/my-gitops-repo.git",
local_path: "/var/lib/bc_gitops",
branch: "main",
reconcile_interval: 60_000,
apps_dir: "apps",
runtime_module: MyAppRuntimeStep 5: Start the Application
Add bc_gitops to your application's dependencies and start it:
application:ensure_all_started(bc_gitops).bc_gitops will:
- Clone the repository (or pull if already cloned)
- Parse all application specifications in
apps/ - Compare desired state with current state
- Deploy/upgrade/remove applications as needed
- Repeat every
reconcile_intervalmilliseconds
Step 6: Monitor and Operate
Check Status
{ok, Status} = bc_gitops:status().
%% #{status => synced,
%% last_commit => <<"abc123...">>,
%% app_count => 5,
%% healthy_count => 5}Trigger Manual Reconciliation
bc_gitops:reconcile().View States
%% Desired state (from git)
{ok, Desired} = bc_gitops:get_desired_state().
%% Current state (running)
{ok, Current} = bc_gitops:get_current_state().
%% Specific app
{ok, AppState} = bc_gitops:get_app_status(my_web_app).Deployment Workflow
Once bc_gitops is running, your deployment workflow becomes:
- Make changes to your application specifications in git
- Commit and push to the tracked branch
- Wait for bc_gitops to detect changes (or trigger manually)
- Verify the deployment via status API or telemetry
# Update version in apps/my_web_app/app.config
# Change: version => <<"1.0.0">> to version => <<"1.1.0">>
git add .
git commit -m "Upgrade my_web_app to 1.1.0"
git push
bc_gitops will automatically detect the change and upgrade the application.
Next Steps
- Read the Runtime Implementation Guide for advanced deployment strategies
- Set up Telemetry handlers for monitoring
- Configure Git authentication for private repositories