Say you’ve got a pipeline for building a new image, but you’d like to also make sure the Docker host redeploys the app whenever it gets updated. (I’m not going to go into how to actually do that (yet?) but this will cover at least being able to SSH and run an arbitrary command.)

Host setup

Create a service account user:

sudo useradd -r -c "Gitlab Deployment User" -s $(which bash) -m gitlab-deploy

Generate a keypair:

sudo -u gitlab-deploy ssh-keygen -t ed25519

base64 encode the private key:

sudo -u gitlab-deploy bash -c 'cat ~/.ssh/id_ed25519 | base64 -w0'

Copy the output and add it as a masked Gitlab CI variable called SSH_PRIVATE_KEY_B64.

It needs to be base64 encoded because Gitlab refuses to mask variables containing whitespace. 🙄

copy/append the public key to ~/.ssh/authorized_keys so the account can log in with the key:

sudo -u gitlab-deploy bash -c 'cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys'
sudo -u gitlab-deploy bash -c 'chmod 600 ~/.ssh/authorized_keys'

From another machine, capture the public keys of the remote host:

ssh-keyscan -H remote-host.example.com

Copy the output and save it as a file type Gitlab CI variable called “SSH_KNOWN_HOSTS”.

Gitlab setup

In addition to the SSH_PRIVATE_KEY_B64 and SSH_KNOWN_HOSTS CI variables, also create one called SSH_USER_HOST which contains something like gitlab-deploy@remote-host.example.com.

Then use a job like this:

deploy-job:
  stage: deploy 
  image: alpine:latest
  before_script:
    - apk update && apk add openssh-client
    - mkdir -p ~/.ssh 
    - eval $(ssh-agent -s)
    - echo $SSH_PRIVATE_KEY_B64 | base64 -d | tr -d '\r' | ssh-add -
    - cp $SSH_KNOWN_HOSTS ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
  script:
    - ssh $SSH_USER_HOST echo "Hello from \$HOSTNAME"