GitLab Deploy Keys instead of Access Tokens, and Deploy Tokens
tl;dr
| What | Protocol | Use Case |
|---|---|---|
| Deploy Key | SSH | Git operations (clone, pull, push) |
| Deploy Token | HTTP | Docker / Registry (docker login, pull) |
| CI Job Token | Runner HTTP | Cross-project pipeline image sharing |
Deploy Keys
I manage a large number of Git repositories across many nested Linux user environments running in rootless namespaces and container-like setups. Access to these environments is done via (e.g.) sudo machinectl shell codimd@ under restricted user contexts, which makes standard SSH workflows such as SSH agent forwarding unreliable or not possible at all.
As a result, maintaining access to Git remotes for frequent pushes becomes quite cumbersome. So far, I relied on personal access tokens hardcoded in HTTPS git origin URLs. I had to replace these tokens regularly because they would expire.
The solution for these contexts: GitLab Deploy Keys. Unlike tokens, deploy keys are project-level SSH keys.
1. Generate SSH key
Generate standard default keys (works automatically across all repos for this user):
ssh-keygen -t ed25519 -N ""
2. Show public key
cat ~/.ssh/id_ed25519.pub
3. Add key in GitLab
Project → Settings → Repository → Deploy keys
- Paste public key
- Check: “Write access allowed”
- Click Add key
Configure SSH (Custom port & Sudo-Git edge cases)
A. Non-standard SSH Port
If your GitLab SSH runs on a non-standard port (e.g. 11111), configure it in ~/.ssh/config so you can keep standard Git remote URLs:
cat << 'EOF' >> ~/.ssh/config
Host gitlab.example.com
Port 11111
User git
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
EOF
chmod 600 ~/.ssh/config
B. If Git runs as sudo (e.g., container / mixed UID ownership)
If your user aliases git to sudo git due to rootless/container file mounts, SSH will look in /root/.ssh/. Mirror the key and config to root:
sudo cp -r ~/.ssh /root/ && sudo chown -R root:root /root/.ssh
4. Switch repo remote to SSH
git remote set-url origin git@gitlab.example.com:<group>/<repo>.git
5. Verify and Push
# Quick connection check (should return: Welcome to GitLab, @...)
ssh -T git@gitlab.example.com
# Push
git push
Reusing an Existing Deploy Key in a New Repository
GitLab rejects pasting the same public key twice (“Fingerprint has already been taken”). To reuse an existing server key:
1. Find the Fingerprint of the Active Key
ssh-keygen -lf ~/.ssh/id_ed25519.pub
2. Enable the Key in the New Project
Do not click “Add new key”.
- Go to: Settings → Repository → Deploy keys.
- Switch to the tab: Privately accessible deploy keys.
- Search for your SHA256 fingerprint and click Enable.
3. Grant Write Access
By default, reused keys are restricted to read-only.
- Scroll up to Enabled deploy keys.
- Click the Pencil icon (Edit) next to the key.
- Check: Grant write permissions to this key → Save changes.
Deploy Tokens
A Git Deploy Key (SSH) only works for Git operations. For the Docker Registry, HTTP-based authentication is needed.
The easiest, most secure way to do this for a server is a GitLab Deploy Token.
1. Create a Deploy Token in GitLab
- Go to your GitLab project: Settings → Repository.
- Expand the Deploy tokens section.
- Click Add token:
- Name:
Server-Pull-Token - Username: (Leave blank, it will auto-generate)
- Scopes: Check
read_registryonly.
- Name:
- Click Create deploy token and copy the username/password immediately.
2. Log in on your VM and use it
echo "PASSWORD" | docker login registry.example.com -u "USERNAME" --password-stdin
Credentials are saved in ~/.docker/config.json. Your Compose commands now work seamlessly:
docker compose pull
docker compose up -d
CI/CD Job Tokens (Cross-Project Registry Access)
While Deploy Keys handle Git operations and Deploy Tokens handle server Docker pulls, GitLab isolates pipelines across repositories by default. If a pipeline in Project B tries to pull an image from Project A’s Container Registry, it will fail.
1. Add Consuming Project to Allowlist (In Project A)
- Go to: Settings → CI/CD.
- Expand Job token permissions (or CI/CD job token allowlist).
- Click Add project → Enter target project path (
group/project-b).
2. Pulling the Image in the CI/CD Pipeline (In Project B)
In Project B’s .gitlab-ci.yml, authenticate directly using the ephemeral token:
stages:
- pull-and-run
job:
stage: pull-and-run
image: docker:latest
services:
- docker:dind
before_script:
- echo "$CI_JOB_TOKEN" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin
script:
- docker pull registry.example.com/group/project-a/my-image:latest