Back to Blog

SSH Based Deployments in GitLab

GitLab
Deployments
SSH
Hetzner

Looking to streamline your deployment process for Docker Compose services via GitLab pipelines? This guide walks you through deploying services on any server with SSH access, using GitLab to automate and simplify your workflow. Whether you're a seasoned developer or just diving into DevOps, this tutorial has the steps you need to get up and running smoothly!

Initial server preparation

Before you can start interacting with your server you will need to prepare it. In my case, I used Hetzner as they offer very cheap solid servers with a great interface.

Generate SSH key

You will need to generate an SSH key, one specifically for the deployments. Ideally, this would be different from the key you use to SSH into the server. This guide from GitLab is pretty good and should get you started.

Add SSH key to authorized_keys

To be able to communicate with the server and that it allows connections using this ssh key you will need to add it to authorized_keys.

Installing docker & docker compose

You will need to ensure docker is running on your server, and that docker compose also works. You can follow this guide which explains it very well for all the platforms.

The simple way to test this is to first verify docker can run via:

docker run hello-world

And after that reports all systems ok check your version of docker compose:

docker compose version

Adding GitLab registry and authenticating

You will need to login to the GitLab registry and setup a credentials store. The initial login can be easily done via:

echo "<token>" | docker login registry.gitlab.com -u "<username>" --password-stdin

Here are some useful guides for the different credential store options. This is how your .docker/config.json would look like usually.

{
    "auths": {
        "registry.gitlab.com": {}
    },
    "credsStore": "pass"
}

GitLab Deployment Pipeline

Once you have prepped your server you can verify everything works by creating the first part of the pipeline which SSHes into the server. Provide the SSH settings in the GitLab CI/CD Variables

Base SSH Pipeline

The first part just sets up our ability to SSH into the server and is easily generalizable to multiple servers.

.deploy:
  stage: deploy
  image: ubuntu  
  variables:
    # you could use whatever user you setup initially
    SSH_USER: "root" 
    # this variable will come from the CI settings of the repo/group
    SSH_HOST: "$SERVER_IP"  
  before_script:
    # ensure SSH is installed in the job
    - apt-get update -y && apt-get install openssh-client -y
    - eval `ssh-agent -s`
    
    # set the private key which will come from the CI settings
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    
    # setup the ssh settings to avoid warnings about new hosts
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - ls -la ~/.ssh
    
    # this step will already fail if your server does not accept the SSH Key
    - ssh-keyscan $SSH_HOST >> ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts

Deployment via Docker Compose

We want the deployment to stop the previous containers if they exist, copy over the docker-compose file(s), and then run the up -d command. The --remove-orphans command ensures if we change some services in the docker compose and they become orphaned they will get removed.

deploy:core:
  extends: .deploy
  variables:
    SERVER_PATH: "/root/deployment/core/"
  script:
    # stop the containers
    - ssh $SSH_USER@$SSH_HOST "cd $SERVER_PATH && [ -f docker-compose.yml ] && \
        docker compose down || true"

    # copy over all the files
    - scp -r . $SSH_USER@$SSH_HOST:$SERVER_PATH

    # restart the containers
    - ssh $SSH_USER@$SSH_HOST "cd $SERVER_PATH && \
        docker compose pull && \
        docker compose up -d --remove-orphans"

Environment variables & secrets

Suppose you need to also copy over the environment variables such as passwords and other variables.

NOTE: You need to have these variables defined in your GitLab CI/CD settings

deploy:core:
  extends: .deploy
  script:
    # Inject postgres password
    - sed -i '/^POSTGRES_PASSWORD=/d' .env
    - printf "\nPOSTGRES_PASSWORD=%s\n" "$POSTGRES_PASSWORD" >> .env
    - printf "\nMONGO_PASSWORD=%s\n" "$MONGO_PASSWORD" >> .env
    - printf "\nREDIS_PASSWORD=%s\n" "$REDIS_PASSWORD" >> .env

Or even use this crafty script which will take an .env.example and extract all the variables defined in the example.

https://gitlab.com/-/snippets/3763150/raw/main/prepare_env.sh