GitLab Deploy Keys instead of Access Tokens, and Deploy Tokens

tl;dr

WhatProtocolUse Case
Deploy KeySSHGit operations (clone, pull, push)
Deploy TokenHTTPDocker / Registry (docker login, pull)
CI Job TokenRunner HTTPCross-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”.

  1. Go to: Settings → Repository → Deploy keys.
  2. Switch to the tab: Privately accessible deploy keys.
  3. Search for your SHA256 fingerprint and click Enable.

3. Grant Write Access

By default, reused keys are restricted to read-only.

  1. Scroll up to Enabled deploy keys.
  2. Click the Pencil icon (Edit) next to the key.
  3. 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

  1. Go to your GitLab project: Settings → Repository.
  2. Expand the Deploy tokens section.
  3. Click Add token:
    • Name: Server-Pull-Token
    • Username: (Leave blank, it will auto-generate)
    • Scopes: Check read_registry only.
  4. 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)

  1. Go to: Settings → CI/CD.
  2. Expand Job token permissions (or CI/CD job token allowlist).
  3. 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