
GitHub Deploy Keys Are Unique by Public Key, Not by Repo
GitHub rejects a deploy key already attached to another account or repository. Here is how to identify the conflict, create a new key, and reconnect Coolify.
If GitHub returns key is already in use, the public key is already attached to another account or repository. A deploy key attached to one repository cannot be reused on another. Find the existing attachment or generate a fresh keypair for the new repository, then update the corresponding Coolify source configuration.
How to fix GitHub key is already in use in Coolify
- Check whether the public key is attached to a GitHub account or another repository. GitHub's error guide lists both cases.
- If it is a deploy key for another repository, leave it there and generate a new keypair for the new repository.
- Add the new private key to Coolify and the matching public key to the target GitHub repository.
- Test repository access with the new key before starting a deployment.
I learned this while setting up a fresh Coolify app for jdmcasanova/resume-website. The site is a simple static page, but it pulls from a private GitHub repo. I already had a deploy key on Coolify called 'draftedby-github-deploy' (UUID cc4ko8s08gg8g88wsog8g0og). It was already working for one of my other repos. One less key to manage, one less thing to track. I figured I would just reuse it.
I called the GitHub API to add that same public key to the new repo. POST to /repos/jdmcasanova/resume-website/keys with the public key value. The response came back as HTTP 422 with a JSON body: { "code": "custom", "field": "key", "message": "key is already in use" }. No further explanation. No hint that the key was globally unique. Just a generic validation error.
The lesson: the same SSH public key cannot be a deploy key on two repositories. It does not matter if the key has a different title or purpose. GitHub documents the rule in its troubleshooting guide, but the generic 422 response does not point you there.
The workaround is straightforward. Generate a fresh ed25519 keypair per repo. I ran this on my local machine:
ssh-keygen -t ed25519 -N '' -C 'coolify-jdmcasanova-deploy' -f /tmp/jdmcasanova-deploy
This creates two files: /tmp/jdmcasanova-deploy (private key) and /tmp/jdmcasanova-deploy.pub (public key). I then uploaded the private key to Coolify via their API:
POST /api/v1/security/keys
With the private key content in the body. Then I added the public key to the GitHub repo using the gh CLI:
gh api -X POST repos/jdmcasanova/resume-website/keys \
-f title='Coolify jdmcasanova-deploy' \
-f key=$PUB \
-F read_only=true
The whole process took three minutes once I knew the constraint. The time sink was the first time, when I spent 20 minutes debugging the 422 and wondering if my API request was malformed or if the key was already attached somewhere else I had forgotten.
Why does this matter? In the GitHub UI, you attach a deploy key to a specific repository, so reusing the same key elsewhere can feel harmless. GitHub refuses the second attachment. If you manage several sites and try to simplify key management by reusing one public key, the result is the same generic 422.
There are alternatives. You could use a GitHub App with installation permissions. That gives you per-repo access with a single key managed by the app. But for a solo founder managing six small sites, that is overkill. You have to register the app, install it on each repo, and manage the app's private key separately. Another option is a machine user, a separate GitHub account that owns a single deploy key. That is also overkill for a solo operation. A third option is a personal access token (PAT) with repo scope. That works, but it is less secure because the PAT grants broader access than a deploy key. A PAT can read and write to all repos the token has scope for. A deploy key is read-only by default and scoped to one repo. The deploy key is the right shape for this problem. You just need to generate a fresh one per repo.
If you are in the middle of a migration, like I was when moving from Vercel to Coolify, this constraint can trip you up. I wrote about my Vercel-to-Coolify migration in more detail. The deploy key gotcha was one of several small friction points that added up.
For now, the practical takeaway is simple: when you wire a new repo to a CI system, generate a new SSH keypair. Do not try to reuse one. It takes three minutes, and it avoids a 422 that the docs do not prepare you for. The constraint is not a bug. It is a security design choice that makes each deploy key an independent credential. If one repo is compromised, the other repos are not exposed. That is a good thing. The problem is that the error message and the documentation do not explain the constraint clearly. Once you know it, you work around it easily.