Introducing a Go Package to Fedora

Table of Contents

How to Introduce a Go Package to Fedora

If you maintain an open-source Go project and want it available via dnf install on Fedora, the path is well-defined but not always obvious. The Fedora packaging ecosystem has decades of conventions, and Go projects bring their own particularities — vendored dependencies, static linking, and a toolchain that moves fast.

This article documents the full journey from upstream Go project to Fedora stable repository, based on my experience introducing complyctl and complytime-providers to Fedora. My goal is to save you the hours I spent reading scattered documentation and figuring out the right path.

Before You Start

Prerequisites

You need two things before proposing a package to Fedora:

  1. A Fedora Account (FAS): Create one at accounts.fedoraproject.org . This is your identity across all Fedora infrastructure.

  2. Membership in the packager group: Follow the Joining the Package Maintainers guide. This involves signing the FPCA (Fedora Project Contributor Agreement) and being sponsored by an existing packager.

If you are not yet a Fedora packager, you can still prepare your spec
and SRPM. Ask a sponsor to review your work — the sponsorship process
often starts by demonstrating that you can produce a well-formed package.

Install the Tools

On a Fedora machine:

sudo dnf install fedora-packager fedora-review go-vendor-tools \
    rpm-build rpmlint rpmdevtools
sudo usermod -a -G mock $USER
newgrp mock

The key tools:

Tool Purpose
fedora-review Automated review checklist + mock build
go-vendor-tools Vendored license detection and archive generation
rpmbuild Build SRPMs and binary RPMs locally
rpmlint Lint RPMs for common packaging errors
mock Build in a clean chroot (simulates koji)
spectool Download sources declared in the spec
rpmdev-setuptree Create the ~/rpmbuild/ directory tree

Understanding Go in Fedora

The Vendoring Question

In most languages, Fedora packaging guidelines recommend consuming dependencies from the Fedora ecosystem. Python packages use Fedora’s python3-* RPMs. Ruby packages use Fedora’s rubygem-* RPMs. The idea is that security updates to a shared library benefit all packages that depend on it.

Go is different. The Fedora Go SIG (Special Interest Group) explicitly allows — and effectively expects — vendoring. This means your Go module’s vendor/ directory is shipped as part of the source, and dependencies are not packaged separately as individual Fedora RPMs.

This is a pragmatic decision. The Go ecosystem has thousands of small
modules, many with rapid release cycles. Packaging each one individually
would be unsustainable, and Go's static linking means shared libraries
don't provide the same security benefit as in C/Python/Ruby.

The go-vendor-tools Ecosystem

Since vendored dependencies are bundled inside your package, Fedora needs a way to:

  1. Track which licenses are bundled — the RPM License: field must include ALL vendored licenses
  2. Verify license consistency — the build must fail if the license config is stale
  3. Generate bundled Provides: — so dnf knows what’s inside

This is handled by go-vendor-tools , a Fedora-specific toolset that integrates with the RPM build macros. You will create a go-vendor-tools.toml configuration file that declares the license detector and any manual overrides for licenses that can’t be auto-detected.

Key Fedora Go Macros

The Fedora Go packaging macros replace raw go build commands with standardized, hardened equivalents:

Macro Replaces What it does
%gometa -f Manual URL/Source setup Resolves forge metadata (GitHub URL, source tarball)
%gosource Raw GitHub archive URL Generates the correct source URL from forge metadata
%goprep -A tar xf + directory setup Prepares the build tree without inline vendor
%gobuild go build Builds with PIE, RELRO, FORTIFY, debuginfo generation
%gocheck2 go test Runs tests with proper flags and vendor mode
%autorelease Release: 1%{?dist} Automatic release numbering

The %gobuild macro is particularly important — it applies Fedora’s hardening flags (position-independent executables, full RELRO, FORTIFY_SOURCE) and generates proper DWARF debuginfo. Without it, your package would lack debuginfo packages and miss security hardening.

The RPM Spec File

Anatomy of a Go Spec

Here is the structure of a well-formed Fedora Go spec file, annotated with the purpose of each section:

# SPDX-License-Identifier: Apache-2.0

# Toggle for running tests (disable with --without check)
%bcond check 1

# Application-specific constants
%global app_dir myapp

# Forge metadata — goipath + Version are used by %gometa
%global goipath github.com/org/project
Version:        1.0.0
%gometa -f

Name:           myproject
Release:        %autorelease
Summary:        One-line description of your project

# SPDX expression covering ALL vendored licenses
License:        Apache-2.0 AND BSD-3-Clause AND MIT
URL:            %{gourl}
Source0:        %{gosource}
Source1:        %{archivename}-vendor.tar.bz2
Source2:        go-vendor-tools.toml

BuildRequires:  go-vendor-tools
ExclusiveArch:  %{go_arches}

%description
Longer description of what your project does.

%prep
%goprep -A
%setup -q -T -D -a1 %{forgesetupargs}
%autopatch -p1

%generate_buildrequires
%go_vendor_license_buildrequires -c %{S:2}

%build
export GO_LDFLAGS="-X main.version=%{version}"
%gobuild -o %{gobuilddir}/bin/myapp %{goipath}/cmd/myapp

%install
%go_vendor_license_install -c %{S:2}
install -d -m 0755 %{buildroot}%{_bindir}
install -p -m 0755 %{gobuilddir}/bin/myapp %{buildroot}%{_bindir}/myapp

%check
%go_vendor_license_check -c %{S:2}
%if %{with check}
%gocheck2
%endif

%files -f %{go_vendor_license_filelist}
%doc README.md
%{_bindir}/myapp

%changelog
* Wed Sep 02 2026 Your Name <[email protected]> - 1.0.0-1
- Initial Fedora packaging
The Version: line must appear BEFORE %gometa -f because the forge
macros need it to construct the source URL. This is a common gotcha
that produces confusing errors if you place Version after %gometa.

Creating go-vendor-tools.toml

The license configuration file tells go-vendor-tools how to detect vendored licenses:

# First, check which licenses askalono can auto-detect:
go_vendor_license report all

# Look for any "Unknown" entries — those need manual overrides

Create go-vendor-tools.toml:

[archive]

[licensing]
detector = "askalono"

# Add overrides for licenses askalono can't auto-detect.
# Common case: go.yaml.in/yaml/v3 has a dual-license file
# that askalono can't parse.
[[licensing.licenses]]
path = "vendor/go.yaml.in/yaml/v3/LICENSE"
sha256sum = "d18f6323..."
expression = "MIT AND (MIT AND Apache-2.0)"

Generate the sha256sum:

sha256sum vendor/go.yaml.in/yaml/v3/LICENSE

Verify the final expression:

go_vendor_license --config go-vendor-tools.toml report expression

This expression goes into the spec’s License: field.

If your vendored dependencies change between releases (e.g., a module
migrates from gopkg.in/yaml.v3 to go.yaml.in/yaml/v3), the license
override path in go-vendor-tools.toml must be updated. The
%go_vendor_license_check macro in %check catches this at build time,
but it's easy to miss during development.

Fedora Version Compatibility

If your go.mod requires a Go version newer than what a supported Fedora release ships, you may need a compatibility workaround. For example, Fedora 43 shipped only Go 1.25 for some time while my project was already updated to require Go 1.26 in Upstream. This is already solved by the time I am writing, but good to keep the reference:

%prep
%goprep -A
%setup -q -T -D -a1 %{forgesetupargs}
%autopatch -p1

# TODO(2027-01): remove F43 workaround — Fedora 43 EOL: 2026-12-09
%if 0%{?fedora} == 43
sed -i 's/^go [0-9].*/go 1.25/' go.mod
sed -i '/^## explicit; go /s/go [0-9]\..*/go 1.25/' vendor/modules.txt
%endif

Always add a TODO comment with the EOL date so the workaround gets cleaned up.

Building and Testing Locally

Generate the SRPM

With a tagged upstream release and a complete spec, generate the SRPM:

# 1. Prepare the rpmbuild tree
rpmdev-setuptree

# 2. Download the source tarball (to the current directory)
spectool -g myproject.spec

# 3. Generate the vendor archive
go_vendor_archive create --config go-vendor-tools.toml myproject.spec

# 4. Copy everything to rpmbuild SOURCES
cp myproject-*.tar.gz ~/rpmbuild/SOURCES/
cp myproject-*-vendor.tar.bz2 ~/rpmbuild/SOURCES/
cp go-vendor-tools.toml ~/rpmbuild/SOURCES/

# 5. Build the SRPM
rpmbuild -bs myproject.spec
spectool downloads to the current directory (without the -R flag),
and go_vendor_archive also expects the source tarball in the current
directory. Run both from the same working directory to avoid
"file not found" errors.

Full Binary Build

rpmbuild --rebuild ~/rpmbuild/SRPMS/myproject-*.src.rpm

This performs the complete build cycle: %prep, %build, %check, %install, and RPM generation. If it succeeds, your spec is sound.

Verify with rpmlint

rpmlint ~/rpmbuild/SRPMS/myproject-*.src.rpm \
    ~/rpmbuild/RPMS/x86_64/myproject-*.rpm

Common false positives for Go packages:

  • spelling-error for tool names (e.g., complyctl, gRPC) — these are real names, not typos
  • invalid-url Source1 — the vendor archive is a local file generated by go_vendor_archive, not a URL
  • unused-direct-shlib-dependency libresolv.so.2 — Go’s net package links libresolv via cgo for DNS resolution; this is a known Go packaging quirk and is accepted by Fedora reviewers

Test in Mock

mock -r fedora-rawhide-x86_64 rebuild ~/rpmbuild/SRPMS/myproject-*.src.rpm

Mock builds in a clean chroot, simulating the koji build environment. This catches issues that local builds miss (e.g., undeclared build dependencies). Ensure your user is in the mock group (newgrp mock if you just added yourself).

The Fedora Review Process

Filing the Review Request

Upload your spec and SRPM to a publicly accessible location. Fedora provides fedorapeople.org for packagers:

scp myproject.spec ~/rpmbuild/SRPMS/myproject-1.0.0-1.fc44.src.rpm \
    fedorapeople.org:public_html/

Then file a bug at bugzilla.redhat.com :

Field Value
Product Fedora
Component Package Review
Version rawhide
Summary Review Request: myproject - Short description

In the description:

Spec URL: https://yourname.fedorapeople.org/myproject.spec
SRPM URL: https://yourname.fedorapeople.org/myproject-1.0.0-1.fc44.src.rpm
Description: What your project does.
Fedora Account System Username: yourfasid

What the Reviewer Does

The reviewer runs:

fedora-review -b <BZ_NUMBER>

This tool automatically:

  1. Downloads the spec and SRPM from the URLs in the bug
  2. Builds the package in mock
  3. Runs rpmlint on all produced RPMs
  4. Generates a review checklist with MUST/SHOULD/EXTRA items
  5. Verifies source checksums against upstream

The output is a structured checklist where each item is marked:

  • [x] = automated pass
  • [ ] = manual review needed
  • [!] = fail
  • [-] = not applicable

Helping Your Reviewer

The review process requires at least one person who is NOT the package author to review and approve. To make their job easier, here are some optionals:

  1. Run fedora-review yourself first and identify any issues before filing the BZ
  2. Post a proactive self-review as a BZ comment, documenting the rationale for each manual check item. But make it clear this does not replace the neutral review
The review checklist can look intimidating with many [ ] items. Most
of them are trivially passable — the reviewer reads the spec, verifies
each item, and marks it [x]. A good proactive self-review with clear
explanations can reduce a multi-hour review to under 30 minutes.

After Approval

Once the reviewer sets the fedora-review+ flag:

  1. Request the dist-git repo: The reviewer (or you) runs fedpkg request-repo myproject <BZ_NUMBER>
  2. Pagure repo is created at src.fedoraproject.org/rpms/myproject
  3. Initial import: Push your spec, sources, and patches to the repo
  4. Build in koji: fedpkg build
  5. Submit Bodhi update: fedpkg update

Coordinating Multiple Packages

If your project depends on another package that is also new to Fedora (or needs a major version update), you face a coordination challenge. This is common in Go projects where a CLI tool depends on separately-packaged plugins.

Build vs Runtime Dependencies

The key insight for Go packages is that BuildRequires and Requires have different implications:

  • BuildRequires must be in the Fedora buildroot (koji) at build time
  • Requires must be in the Fedora repos at install time

Since Go dependencies are vendored, your inter-package dependency is likely a runtime-only Requires, not a BuildRequires. This means your package can be built and reviewed independently, even if the dependency isn’t yet updated in Fedora.

Multi-Build Bodhi Updates

Bodhi supports grouping multiple package builds into a single update. This means two packages can arrive in updates-testing and then stable as an atomic unit:

# After both packages are built in koji:
bodhi updates new --type enhancement \
    --notes "Coordinated update: myproject + myplugins" \
    myproject-1.0.0-1.fc44 myplugins-1.0.0-1.fc44

This ensures users never see a broken intermediate state where one package is updated but the other isn’t.

Automating Future Updates with Packit

Once the initial package is in Fedora, future updates can be automated with Packit . Add a .packit.yaml to your upstream repo:

downstream_package_name: myproject
upstream_tag_template: v{version}

jobs:
  - job: propose_downstream
    trigger: release
    dist_git_branches:
      - fedora-rawhide
      - fedora-stable

  - job: koji_build
    trigger: commit
    dist_git_branches:
      - fedora-rawhide
      - fedora-stable

  - job: bodhi_update
    trigger: commit
    dist_git_branches:
      - fedora-stable

When you create a new GitHub release, Packit will automatically:

  1. Propose a PR to dist-git with the updated spec
  2. After merge, trigger a koji build
  3. Submit a Bodhi update for branched Fedora releases

This reduces the maintenance burden to reviewing and merging the automated PRs.

Key References

Here is a curated list of the most useful documentation. The Fedora docs are extensive and sometimes circular — these are the pages I actually needed:

Topic URL
New Package Process docs.fedoraproject.org/…/New_Package_Process_for_Existing_Contributors
Package Review Process docs.fedoraproject.org/…/Package_Review_Process
Review Guidelines docs.fedoraproject.org/…/ReviewGuidelines
Go Packaging Guidelines docs.fedoraproject.org/…/Golang
go-vendor-tools docs fedora.gitlab.io/sigs/go/go-vendor-tools
Fedora Go SIG fedoraproject.org/wiki/SIGs/Go
Package Maintenance Guide docs.fedoraproject.org/…/Package_Maintenance_Guide
Packit packit.dev
Bodhi bodhi.fedoraproject.org

Conclusion

Introducing a Go package to Fedora is a structured process with clear steps. The first time is the hardest — understanding the vendoring model, the Go-specific macros, the review process, and the toolchain takes time. But once you’ve done it, subsequent packages follow the same pattern, and tools like Packit automate the ongoing maintenance.

The Fedora Go packaging ecosystem is well-designed. The go-vendor-tools + %gobuild + %gocheck2 combination handles the hard parts: license compliance, hardening flags, and test execution. Your job is to write a clean spec, verify it locally, and help your reviewer understand the package.

I hope this guide saves you some of the time I spent navigating the documentation the first time around. The Fedora community is welcoming and the infrastructure is solid — getting your Go project into Fedora is worth the effort.