Skip to content
Configuration

Configuration

forgemirror reads one YAML file. Any key can be overridden with an environment variable or a CLI flag, and every secret can be read from a file.

Sources and precedence

Later sources win:

  1. Built-in defaults.
  2. The configuration file.
  3. Environment variables: prefix FORGEMIRROR_, upper case, with a double underscore for nesting.
  4. CLI flags.
FORGEMIRROR_SERVER__LISTEN_ADDRESS=:9000
FORGEMIRROR_LOG__LEVEL=debug
FORGEMIRROR_WEBHOOK__SECRET_FILE=/run/secrets/forgemirror/webhook-secret

Config file location: the file comes from --config / -c, or from FORGEMIRROR_CONFIG. Without either, forgemirror uses the first of these that exists:

  • ./forgemirror.yaml
  • $XDG_CONFIG_HOME/forgemirror/forgemirror.yaml on Linux, or ~/Library/Application Support/forgemirror/forgemirror.yaml on macOS

Unknown keys are errors. A typo such as max_concurent stops the service at startup instead of being silently ignored. All errors are reported together, so one run shows everything that needs fixing:

forgemirror --config forgemirror.yaml validate

CLI

Flag / commandOverridesDescription
--config, -cConfiguration file (env FORGEMIRROR_CONFIG)
--listen-addressserver.listen_addresse.g. :8080
--log-levellog.leveldebug, info, warn, error
--log-formatlog.formatjson, text
validateLoad and validate the configuration, then exit
--versionVersion, commit and Go version

Secrets

Every secret has two forms: an inline value, or a *_file path to read it from. Set one or the other; setting both is an error. Surrounding whitespace in files is trimmed. Prefer the _file form wherever files can be mounted, such as Kubernetes Secrets, Docker secrets or systemd credentials.

InlineFrom file
webhook.secretwebhook.secret_file
source.tokensource.token_file
targets[].tokentargets[].token_file

Secrets never appear in logs, error messages or on disk. For how tokens reach git, see Security.

Targets

A target is a forge that receives the mirror. url is a Go template, rendered for each repository with these fields:

FieldExample
{{ .Owner }}acme
{{ .Name }}app
{{ .FullName }}acme/app
targets:
  - name: github            # unique; used in logs and metrics
    url: "https://github.com/acme/{{ .Name }}.git"
    username: oauth2        # default; GitHub, Codeberg and GitLab accept it
    token_file: /run/secrets/forgemirror/github-token
  - name: codeberg
    url: "https://codeberg.org/{{ .Owner }}/{{ .Name }}.git"
    token_file: /run/secrets/forgemirror/codeberg-token

Each URL is checked at startup:

  • It must render, so unknown fields are rejected.
  • It must use http or https and include a host.
  • It must not contain credentials. Those belong in token.
Target repositories must already exist. forgemirror pushes all branches and tags with --prune, so the target becomes an exact copy and branches deleted on Forgejo disappear there too. Pull request refs are not pushed, because GitHub rejects them.

Filter

Without a filter section, every push is mirrored. Each configured rule must pass; the first rule that fails is reported in the webhook response and the log.

RuleEffect
include_branchesOnly pushes to these branches trigger a mirror. Tag pushes always do.
require_topicOnly repositories with this Forgejo topic, for per-repository opt-in.
exclude_privateSkip private repositories.
exclude_forksSkip forks.
include_reposOnly repositories whose owner/name matches.
exclude_reposSkip repositories whose owner/name matches; this wins over include_repos.

Patterns are Go regular expressions that must match the whole value: app matches only app, not my-app; use .*app for that.

filter:
  include_repos: "acme/.*"
  exclude_repos: "acme/(secrets|infra-.*)"
  include_branches: ["main", "release/.*"]
  exclude_forks: true
  require_topic: mirror

A filter only decides whether a push triggers a mirror. The mirror itself always copies all branches and tags.

Concurrency and timeouts

KeyDefaultMeaning
mirror.max_concurrent4Maximum git processes (clones and pushes) running at once, across all repositories
mirror.timeout10mUpper bound for each git command
server.shutdown_timeout60sBudget for a graceful shutdown, covering open requests and running mirrors

Only one mirror run per repository is active at a time. Pushes that arrive during a run are coalesced into one follow-up run.

Full reference

The annotated example file from the repository, with all keys and their defaults:

# forgemirror example configuration. Copy to forgemirror.yaml and adjust.
#
# Every key can also be set via environment variables: prefix FORGEMIRROR_,
# upper case, nesting with a double underscore, e.g.
#   FORGEMIRROR_SERVER__LISTEN_ADDRESS=:9000
#   FORGEMIRROR_WEBHOOK__SECRET_FILE=/run/secrets/webhook
# Precedence: defaults < this file < environment < CLI flags.
# Values shown are the defaults unless marked "required".

server:
  listen_address: ":8080"
  read_header_timeout: 10s
  # Upper bound for a graceful shutdown, including running mirrors.
  shutdown_timeout: 60s

log:
  level: info # debug, info, warn, error
  format: json # json, text

webhook:
  # required: the secret configured on the Forgejo webhook. Use either the
  # value or secret_file (e.g. a mounted Kubernetes Secret), not both.
  secret: change-me
  # secret_file: /run/secrets/forgemirror/webhook-secret
  max_body_bytes: 5242880

# Credentials for cloning private repositories from Forgejo. Leave the token
# empty if only public repositories are mirrored.
source:
  username: oauth2
  # token_file: /run/secrets/forgemirror/source-token

mirror:
  # Upper bound for one clone + push to one target.
  timeout: 10m
  # Upper bound for git operations running at the same time.
  max_concurrent: 4
  git_binary: git
  # Where temporary clones are created; empty uses the OS temp directory.
  work_dir: ""

# Which pushes are mirrored. All rules are optional; without them every push
# is mirrored. Patterns are Go regular expressions that must match the whole
# value ("app" does not match "my-app"; use "app.*").
filter:
  # Matched against "owner/name". Exclude wins over include.
  include_repos: ""
  exclude_repos: ""
  # Only pushes to these branches trigger a mirror; tag pushes always do.
  include_branches: [] # e.g. ["main", "release/.*"]
  exclude_private: false
  exclude_forks: false
  # Opt-in: only repositories with this Forgejo topic are mirrored.
  require_topic: "" # e.g. "mirror"

# required: at least one target. `url` is a Go text/template rendered with
# the repository: {{ .Owner }}, {{ .Name }}, {{ .FullName }}.
targets:
  - name: github
    url: "https://github.com/acme/{{ .Name }}.git"
    username: oauth2
    token: change-me
  - name: codeberg
    url: "https://codeberg.org/{{ .Owner }}/{{ .Name }}.git"
    token_file: /run/secrets/forgemirror/codeberg-token
Last updated on