bc_gitops
View SourceBEAM-native GitOps reconciler for OTP applications.
bc_gitops brings the GitOps pattern to the BEAM ecosystem. It monitors a Git repository for application specifications and automatically reconciles the running system to match the desired state—deploying new applications, upgrading versions, and removing deprecated ones.
Why bc_gitops?
Traditional GitOps tools like Flux and ArgoCD are built for Kubernetes. But what if you're running a BEAM cluster without Kubernetes? Or you want tighter integration with OTP's powerful release and hot code upgrade capabilities?
bc_gitops provides:
- Works out of the box - Default runtime fetches packages from hex.pm and git, compiles, and starts them
- Native BEAM integration - Works directly with OTP applications, releases, and supervision trees
- Hot code upgrades - Automatic module reloading with process suspension/resumption
- Erlang & Elixir support - Fetches and compiles both Erlang (rebar3) and Elixir (mix) packages
- Flexible runtimes - Pluggable backend for custom deployment strategies
- Observable - Built-in telemetry events for monitoring and alerting
- Minimal dependencies - Only requires
telemetry, no external services needed
Installation
Add bc_gitops to your list of dependencies in rebar.config:
{deps, [
{bc_gitops, "0.2.0"}
]}.Or for Elixir projects in mix.exs:
def deps do
[
{:bc_gitops, "~> 0.2.0"}
]
endQuick Start
1. Create a GitOps Repository
Create a git repository with your application specifications:
my-gitops-repo/
├── apps/
│ ├── my_web_app/
│ │ └── app.config
│ └── my_worker/
│ └── app.config
└── README.mdEach application has a configuration file:
%% apps/my_web_app/app.config
#{
name => my_web_app,
version => <<"1.0.0">>,
source => #{
type => hex
},
env => #{
port => 8080,
pool_size => 10
},
health => #{
type => http,
port => 8080,
path => <<"/health">>
},
depends_on => []
}.2. Configure bc_gitops
Add configuration to your sys.config:
{bc_gitops, [
{repo_url, "https://github.com/myorg/my-gitops-repo.git"},
{branch, "main"},
{reconcile_interval, 60000}, %% 1 minute
{runtime_module, my_app_runtime}
]}3. Start the Application
The default runtime (bc_gitops_runtime_default) handles everything:
- Fetches packages from hex.pm using rebar3 (Erlang) or mix (Elixir)
- Clones and compiles git repositories
- Performs hot code reloading during upgrades
- Manages code paths automatically
application:start(bc_gitops).bc_gitops will:
- Clone/pull the repository
- Parse application specifications
- Compare desired state with current state
- Deploy, upgrade, or remove applications as needed
- Repeat on the configured interval
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
repo_url | string | required | Git repository URL |
local_path | string | /var/lib/bc_gitops | Local clone path |
branch | string | "main" | Git branch to track |
apps_dir | string | "apps" | Directory containing app specs |
reconcile_interval | integer | 60000 | Reconcile interval (ms) |
runtime_module | atom | bc_gitops_runtime_default | Runtime implementation |
Application Specification Format
Erlang Format (.config)
#{
name => my_app,
version => <<"1.2.3">>,
source => #{
type => hex, %% hex | git | release
url => <<"...">>, %% For git/release
ref => <<"main">>, %% For git (branch/tag/commit)
sha256 => <<"...">> %% For release (integrity check)
},
env => #{
key => value
},
depends_on => [other_app],
health => #{
type => http, %% http | tcp | custom
port => 8080,
path => <<"/health">>,
interval => 30000,
timeout => 5000
}
}.JSON Format (.json)
{
"name": "my_app",
"version": "1.2.3",
"source": {
"type": "hex"
},
"env": {
"key": "value"
},
"depends_on": [],
"health": {
"type": "http",
"port": 8080,
"path": "/health"
}
}API
Manual Operations
%% Trigger immediate reconciliation
bc_gitops:reconcile().
bc_gitops:sync(). %% alias
%% Get status
{ok, Status} = bc_gitops:status().
%% #{status => synced, last_commit => <<"abc123">>, app_count => 5, healthy_count => 5}
%% Get state
{ok, DesiredState} = bc_gitops:get_desired_state().
{ok, CurrentState} = bc_gitops:get_current_state().
%% Check specific app
{ok, AppState} = bc_gitops:get_app_status(my_app).Manual Deployment
%% Deploy manually (bypasses git)
AppSpec = #app_spec{name = my_app, version = <<"1.0.0">>, ...},
bc_gitops:deploy(AppSpec).
%% Remove an app
bc_gitops:remove(my_app).
%% Upgrade to specific version
bc_gitops:upgrade(my_app, <<"2.0.0">>).Programmatic Start
%% Start reconciler with custom config
bc_gitops:start_reconciler(#{
repo_url => <<"https://github.com/myorg/gitops.git">>,
runtime_module => my_runtime
}).
%% Stop reconciler
bc_gitops:stop_reconciler().Telemetry Events
bc_gitops emits the following telemetry events:
| Event | Measurements | Metadata |
|---|---|---|
[bc_gitops, reconcile, start] | - | - |
[bc_gitops, reconcile, stop] | duration | status |
[bc_gitops, reconcile, error] | duration | error |
[bc_gitops, deploy, start] | - | app |
[bc_gitops, deploy, stop] | - | app, result |
[bc_gitops, upgrade, start] | - | app, from_version, to_version |
[bc_gitops, upgrade, stop] | - | app, result |
[bc_gitops, git, pull] | - | repo, branch |
Example handler:
telemetry:attach(
<<"gitops-logger">>,
[bc_gitops, reconcile, stop],
fun(_Event, Measurements, Metadata, _Config) ->
logger:info("Reconcile completed in ~p ms: ~p",
[Measurements, Metadata])
end,
[]
).Default Runtime Features
The default runtime (bc_gitops_runtime_default) is fully functional out of the box:
Package Fetching
- Hex packages: Automatically fetched via rebar3 or mix
- Git repositories: Cloned, compiled, and loaded
- Code path management: Adds compiled ebin directories to the VM
Hot Code Reloading
During upgrades, bc_gitops:
- Suspends processes using
sys:suspend/1 - Reloads changed modules with
code:soft_purge/1+code:load_file/1 - Resumes processes (triggering
code_change/3callbacks) - Falls back to restart if hot reload fails
Supported Project Types
- rebar3 - Erlang projects with
rebar.config - mix - Elixir projects with
mix.exs - erlang.mk - Projects using Makefile
Implementing Custom Runtimes
For production deployments with specific requirements, implement the bc_gitops_runtime behaviour:
-module(my_app_runtime).
-behaviour(bc_gitops_runtime).
-export([deploy/1, remove/1, upgrade/2, reconfigure/1, get_current_state/0]).
deploy(AppSpec) ->
%% Download from private artifact repository
%% Integrate with service discovery
%% Handle secrets injection
{ok, AppState}.
remove(AppName) ->
%% Deregister from service discovery
%% Clean up resources
ok.
upgrade(AppSpec, OldVersion) ->
%% Use release_handler for OTP releases
%% Or custom upgrade logic
{ok, AppState}.
reconfigure(AppSpec) ->
%% Hot config reload
{ok, AppState}.
get_current_state() ->
%% Return current state of all managed apps
{ok, #{}}.See the Runtime Guide for detailed examples.
Git Authentication
bc_gitops uses the system git command, so authentication works through standard git mechanisms:
- SSH keys: Add your deploy key to
~/.ssh/or use ssh-agent - HTTPS: Use git credential helpers or embed credentials in URL (not recommended)
- GitHub Actions: Use
$GITHUB_TOKENwith credential helper
For private repositories, we recommend SSH deploy keys with read-only access.
Architecture
Hot Code Reload Flow
During upgrades, bc_gitops can perform hot code reloading:
The reconciler follows a continuous loop:
- Pull - Fetch latest changes from git repository
- Parse - Read application specifications from
apps/directory - Diff - Compare desired state (git) with current state (runtime)
- Apply - Execute actions: deploy, upgrade, remove, or reconfigure
Contributing
Contributions are welcome! Please read our Contributing Guide before submitting a PR.
License
MIT License - see LICENSE for details.