Skip to content
Latchkey

Signing Git Commits With GPG in CI

git -S signs a commit with the GPG key named in user.signingkey, producing a verifiable commit.

Signed commits prove who authored them. Wiring git to gpg is a few config lines, but CI also needs gpg to run non-interactively, which means loopback pinentry.

What it does

git invokes gpg to create a signature over the commit object. user.signingkey names the key, commit.gpgsign true signs every commit, and gpg.program selects the binary. The signature is what GitHub shows as "Verified".

Common usage

Terminal
git config user.signingkey "$KEY_ID"
git config commit.gpgsign true
git config gpg.program gpg
# sign one commit explicitly
git commit -S -m "Signed commit"
# make gpg non-interactive for git in CI
echo "pinentry-mode loopback" >> "$GNUPGHOME/gpg.conf"

Options

SettingWhat it does
user.signingkey <id>Which GPG key signs commits
commit.gpgsign trueSign every commit automatically
gpg.program gpgPath to the gpg binary git should call
git commit -SSign a single commit on demand
pinentry-mode loopback (gpg.conf)Let git-invoked gpg run without a prompt

In CI

git does not pass --pinentry-mode, so set pinentry-mode loopback in gpg.conf (under GNUPGHOME) and use a key with no passphrase or one cached via the agent. Set GPG_TTY only matters interactively. Confirm the signing key has the email matching the commit author or hosts may not mark it verified.

Common errors in CI

"error: gpg failed to sign the data" with "Inappropriate ioctl for device" means gpg tried to prompt; add pinentry-mode loopback to gpg.conf and use a passphrase-free or agent-cached key. "secret key not available" means user.signingkey does not match an imported secret key. "gpg: skipped: No secret key" is the same root cause.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →