Skip to main content
Skip to main content

Env Sync

b can sync configuration files from upstream git repositories into your project. This is useful for sharing manifests, configuration templates, or any files across repositories while tracking changes and handling conflicts.

Quick start

# Sync files from any git repo
b install github.com/org/infra:/manifests/base/** ./base

# Add to b.yaml for repeated syncing
b install --add github.com/org/infra@v2.0:/manifests/** ./config

# Update all envs from b.yaml
b update

Configuration

Add env entries to the envs section of your b.yaml:

envs:
github.com/org/infra:
version: v2.0
strategy: merge
ignore:
- "*.md"
files:
manifests/base/**:
dest: base/
manifests/hetzner/**:
dest: hetzner/

# Sync all files from HEAD
github.com/org/shared-config:
files:
"**":

That's it — run b update to sync.

Fields

FieldDescriptionDefault
versionGit tag, branch, or commit SHA to pinHEAD
strategyHow to handle local changes: replace, client, or mergereplace
groupGroup name for selective syncing with --groupnone
ignoreGlob patterns to exclude from all file matchesnone
filesMap of glob patterns to destination config (required; use "**" for all)none
onPreSyncShell command to run before syncingnone
onPostSyncShell command to run after syncingnone

Files map

files:
# Bare key — preserve original path structure
manifests/base/**:

# String shorthand — set destination directory
manifests/hetzner/**: hetzner/

# Full config — dest and per-glob ignore
manifests/monitoring/**:
dest: monitoring/
ignore:
- "*.test.yaml"

Glob patterns

PatternMatches
manifests/base/**All files recursively under manifests/base/
**/*.yamlAll .yaml files anywhere in the repo
configs/ingress.yamlA single literal file
charts/*/values.yamlvalues.yaml one directory deep under charts/

Destination paths

The glob prefix is stripped and the dest value is prepended:

Source fileGlobDestResult
manifests/hetzner/deploy.yamlmanifests/hetzner/**hetzner/hetzner/deploy.yaml
configs/ingress.yamlconfigs/ingress.yamlconfig/config/ingress.yaml
README.md**/*.md(none)README.md

Labels

Use the same repo multiple times with different configurations:

envs:
github.com/org/infra#base:
version: v2.0
files:
manifests/base/**:
dest: base/

github.com/org/infra#monitoring:
version: v2.0
strategy: merge
files:
manifests/monitoring/**:
dest: monitoring/

Labels create separate lock entries so each group is tracked independently.


SCP syntax

For one-off syncs without editing b.yaml:

<repo>[@<version>]:/<glob> [<dest>]
b install github.com/org/infra:/manifests/base/** ./base
b install github.com/org/infra@v2.0:/manifests/hetzner/** ./hetzner
b install --add github.com/org/infra@v2.0:/manifests/** ./config

Merge strategies

When upstream files change and you've also modified the local copy, the strategy controls what happens.

replace (default)

Overwrites local files with upstream content. In an interactive terminal, prompts per-file:

  system/config.yaml has local changes.
[r]eplace [k]eep [m]erge [d]iff > _

In non-interactive mode (CI/CD), overwrites without prompting.

client

Keeps local files unchanged when modified. Only new files from upstream are written.

merge

Three-way merge using the previous commit as base:

  1. Base — file at the previously synced commit (from b.lock)
  2. Local — current file on disk
  3. Upstream — file at the new commit

Conflict markers are inserted when both sides changed the same region:

<<<<<<< local
your-value: true
||||||| base
original-value: true
=======
upstream-value: true
>>>>>>> upstream

Falls back to replace when the base version is unavailable.


Managing envs

Status

Check for upstream changes and local drift without syncing:

b env status

Preview

Test what a glob matches before adding it to config:

b env match github.com/org/infra "manifests/base/**" ./base

Remove

b env remove github.com/org/infra
b env remove github.com/org/infra#monitoring
b env remove --delete-files github.com/org/infra

Dry-run

b update --dry-run

Rollback

b update --rollback
b update --rollback github.com/org/infra

Groups

Tag envs for selective syncing:

envs:
github.com/org/infra#base:
group: dev
files:
manifests/base/**:
dest: base/

github.com/org/infra#monitoring:
group: prod
files:
manifests/monitoring/**:
dest: monitoring/
b update --group=dev

Hooks

Run shell commands before or after syncing:

envs:
github.com/org/infra:
onPreSync: "echo 'Starting sync...'"
onPostSync: "kubectl apply -k manifests/"
files:
manifests/**:
dest: manifests/

Hooks run in the project root directory and respect --quiet mode. They are skipped during --dry-run.


File modes

b preserves executable file modes from upstream. Files with git mode 100755 target 0755 permissions; others target 0644. The initial write respects your system umask; subsequent updates normalize permissions to match upstream.


Profiles (optional)

Profiles are an optional feature for upstream repos that want to offer preconfigured file sets to consumers. They are not required — you can always configure envs directly as shown above.

When to use profiles

  • You maintain a shared infrastructure repo and want to offer named presets (e.g. "base", "monitoring", "staging")
  • You want consumers to discover and install your file sets without knowing the directory structure
  • You want to compose presets from smaller building blocks using includes

Publishing profiles

Add a profiles section to your upstream repo's b.yaml:

profiles:
base:
description: "Base Kubernetes manifests"
files:
manifests/base/**:
dest: base/

monitoring:
description: "Prometheus + Grafana stack"
strategy: merge
files:
manifests/monitoring/**:
dest: monitoring/

staging:
description: "Staging preset (base + monitoring)"
includes:
- base
- monitoring
ignore:
- "**/prod-*"

Profile fields are the same as env fields, plus:

FieldDescription
descriptionHuman-readable description shown in b env profiles
includesCompose from other profiles (resolved recursively; later overrides earlier)

Discovering profiles

$ b env profiles github.com/org/infra@v2.1

Available profiles from github.com/org/infra @ v2.1:

base Base Kubernetes manifests
manifests/base/** → base/

monitoring Prometheus + Grafana stack
manifests/monitoring/** → monitoring/

staging Staging preset (base + monitoring)
includes: base, monitoring

Install a profile with:
b env add --version v2.1 github.com/org/infra#<name>

If the upstream repo has no b.yaml, the command falls back to listing the directory structure with suggested install commands.

Installing a profile

# Add a profile to your local b.yaml
b env add github.com/org/infra#monitoring

# Pin a specific version
b env add --version v2.0 github.com/org/infra#base

This copies the profile config into your local envs section. Profiles with includes are automatically flattened. Run b update to sync.

Version: If --version is given, the profile is pinned. If omitted, it tracks HEAD.

Interactive selection

$ b env add -i github.com/org/infra

Available profiles from github.com/org/infra:

[1] base Base Kubernetes manifests
[2] monitoring Prometheus + Grafana stack
[3] staging Staging preset (base + monitoring)

Select profiles (space-separated numbers, e.g. "1 3"): 1 3
Added github.com/org/infra#base to b.yaml
Added github.com/org/infra#monitoring to b.yaml

Run `b update` to sync files.

Requires a terminal (not available in CI/CD).


How it works

  1. Resolve versiongit ls-remote converts the version to a commit SHA.
  2. Check lock — Skip if b.lock already has the same commit.
  3. Clone/fetch — Bare clone to ~/.cache/b/repos/, fetch the target commit.
  4. Match filesgit ls-tree + glob matching, filtering ignored paths.
  5. Detect changes — Compare on-disk SHA256 against b.lock checksums.
  6. Apply strategy — Write files per the configured strategy.
  7. Update lock — Record commit SHA, file checksums, modes, and destinations.

Lock file

b.lock tracks the exact state of every synced env:

{
"envs": [
{
"ref": "github.com/org/infra",
"label": "",
"version": "v2.0",
"commit": "abc123def456...",
"previousCommit": "old789...",
"files": [
{
"path": "manifests/base/deploy.yaml",
"dest": "base/deploy.yaml",
"sha256": "e3b0c44298fc...",
"mode": "644"
}
]
}
]
}

Commit b.lock to version control so your team stays in sync and b verify can detect drift.


Git cache

b cache path              # Show cache location
du -sh $(b cache path) # Check cache size
b cache clean # Remove all cached repos

Uses shallow bare clones (--depth 1) to minimize disk usage.


Tips

  • CI/CD: replace applies silently in non-interactive mode. Use client or merge if your pipeline modifies synced files.
  • Verify: b verify checks all artifacts against b.lock checksums.
  • Conflicts: If two env entries write to the same path, b warns before syncing.
  • Auth: Private repos need GITHUB_TOKEN, GITLAB_TOKEN, or GITEA_TOKEN. See Authentication.
  • Force: b update --force re-syncs even when up-to-date.
  • Unchanged: Files identical to upstream are automatically skipped.
Was this section helpful?