Skip to content
Latchkey

GoReleaser GPG "signing failed" in CI

GoReleaser can sign checksums and artifacts with GPG. Signing fails in CI when the private key was never imported into the runner keyring, or when gpg prompts for a passphrase it cannot get non-interactively.

What this error means

GoReleaser fails at the sign step with "signing failed" and a gpg error such as "No secret key" or "Inappropriate ioctl for device".

Terminal
  ⨯ sign failed  error=sign: signing failed: exit status 2:
  gpg: signing failed: No secret key

Common causes

The signing key is not imported

The private key is not in the runner keyring, so gpg has no secret key to sign with.

gpg cannot read the passphrase

Without --batch and --pinentry-mode loopback, gpg tries to prompt on a tty that CI does not have.

How to fix it

Import the key and configure batch signing

  1. Store the exported private key as a secret and import it in a setup step.
  2. Provide the passphrase via a secret and loopback pinentry.
  3. Point the sign config at the imported key.
.goreleaser.yaml
signs:
  - artifacts: checksum
    args:
      - "--batch"
      - "--pinentry-mode=loopback"
      - "--passphrase=${GPG_PASSPHRASE}"
      - "--sign"

Import the secret key before GoReleaser

Load the private key into gpg so a secret key is available at sign time.

Terminal
echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --batch --import

How to prevent it

  • Import the signing key in a dedicated setup step.
  • Use --batch and loopback pinentry for non-interactive gpg.
  • Keep the key and passphrase in secrets, never in the repo.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →