You have already learned how to build your blog and host it on GitLab Pages. However, you also have the option to host the blog on your own server or webspace. This guide will show you the necessary steps.

The naming conventions for your repository (i.e. <groupname>.gitlab.io) are no longer required for deployment to your own server (unless you also want to use GitLab Pages).

Basic Concept

The files of your blog are created as usual during the build process. Afterwards, the GitLab CI runner connects to your server via SSH. The files are then copied to the server (e.g. using rsync or scp).

In order for the CI/CD server to connect to your server via SSH (Secure Shell), you need an SSH key pair. This consists of a private key and a public key and can be created with the command ssh-keygen.

  • The private key is used to authenticate the source machine (e.g. GitLab CI) that wants to upload data to the target server (e.g. your web server).

  • The public key is added to the target server so it can verify the authentication of the source server.

An example deployment is provided in the .gitlab-ci.yml file of this repository. Simply use the deploy_to_own_server job instead of deploy_to_gitlab_pages as your deployment step.

TL;DR

To host your blog files on your own server, follow these 5 steps. You will find more detailed explanations for each step further down the page.

  1. Create an SSH key pair

    • Create an SSH key pair with ssh-keygen

      ssh-keygen -t rsa -b 4096 -N "" -C "Purpose:  <PROJECT> @ <CI-SERVER> -> <TARGET SERVER>" -f ~/.ssh/id_rsa_blog
    • Save the files of the generated key pair in your password manager (e.g. KeePass)

  2. Prepare the server

    • Add public key to the target server

      • cat ~/.ssh/id_rsa_blog.pub

      • Connect to the target server via SSH

      • Append the key to the authorized_keys file on the server (e.g. nano ~/.ssh/authorized_keys and paste the contents of the public key)

      • Some providers also allow you to add the key via a web interface.

    • Create a folder for the blog

      • e.g. mkdir -p ~/public_html/blog

      • Inside this folder, create the live folder (mkdir -p ~/public_html/blog/live)

    • (Sub-)domain configuration (optional)

      • Point a (sub-)domain to the live folder (e.g. blog.example.com)

      • Create an SSL/TLS certificate for the (sub-)domain (with HTTPS redirection)

  3. Prepare GitLab

    • Goal: Store the private key in the GitLab build server

    • Log in to https://gitlab.com/

    • Go to Settings  CI/CD  Variables in your project

    • Add a variable

      • Visibility: Masked and hidden (Masked: value is hidden in pipeline logs)

      • Set the Protected flag so the variable is only available in protected branches/tags (of course, the branch/tag must be protected. Go to Settings  Repository  Branch rules in your project and protect the branch there)

      • Description: SSH private key for deployment to <TARGET SERVER>

      • Key: (e.g. SSH_PRIVATE_KEY_BLOG_B64)

      • Value: Paste the output of cat ~/.ssh/id_rsa_blog | base64 -w 0 (including the final =)

  4. Configure the pipeline

    • Configure the deploy_to_own_server job in your .gitlab-ci.yml file

    • Set variables in gitlab-ci.yml

  5. Run the pipeline

    • Commit and push the file

    • Check if the job ran successfully / if the files are on the server

Details on the 5 Steps

Create SSH key pair:

Not everyone should be able to upload files to your server, only the GitLab instance, so an authentication method is needed. A good option is to use SSH keys. An SSH key pair can be created with the command ssh-keygen from the openssh-client package.

If you haven’t installed the package yet, you can do so with sudo apt install openssh-client.

Use the following command to create the key:

ssh-keygen -t rsa -b 4096 -N "" -C "Purpose:  <PROJECT> @ <CI-SERVER> -> <TARGET SERVER>" -f ~/.ssh/id_rsa_blog
Component Description

ssh-keygen

Tool to generate a new SSH key pair.

-t rsa

Specifies the type of key - in this case RSA (Rivest, Shamir,Adleman).

-b 4096

Specifies the key length in bits. 4096 bits is a very secure value.

-N ""

Defines an empty passphrase (no password protection for the private key). Useful for CI/CD, as the key can be used automatically without entering a passphrase.

-C "Purpose: <PROJECT> @ <CI-SERVER> → <TARGET SERVER>"

Comment added to the key, e.g. to describe its purpose. (Optional, but helpful to identify the key later, e.g. "Blog on server XY")

-f ~/.ssh/id_rsa_blog

Path and base filename for the key pair. Here, I chose the ~/.ssh/ directory, which is common for SSH keys, and the filename id_rsa_blog to make it clear that this key is for the blog.

This creates two files: ~/.ssh/id_rsa_blog (private key) and ~/.ssh/id_rsa_blog.pub (public key). The private key should not be publicly accessible, but only on the build server (here, GitLab) and your local machine. The public key, on the other hand, is stored on the target server so it can verify the authentication of the source server.

It is best to use a separate key pair for each source server. This allows you to easily revoke access to your server if the key stored on GitLab is compromised. In that case, you only need to remove the corresponding public key from the list of authorized keys on your server. The key pair can also be specific to the project and/or the target server. This is especially useful if you work on projects with multiple people.
You should especially not use the same key pair that you use to log in from your own system to the server. Otherwise, you would lock yourself out of the server as soon as you remove the key from the authorized_keys list to revoke CI server access.

After creating the key pair (here: id_rsa_blog and id_rsa_blog.pub), save the files in a password manager (e.g. KeePass) so you have them available later.

Prepare the server

Next, tell your server that the owner of the private key (i.e. the GitLab server) can connect to the server with this key (i.e. without entering a password). To do this, store the public key on the server in the file ~/.ssh/authorized_keys.

  • Output the contents of the public key: cat ~/.ssh/id_rsa_blog.pub

  • Log in to your server via SSH - e.g. with ssh <USER>@<SERVER>

  • Append the key to the file "~/.ssh/authorized_keys" (e.g. nano ~/.ssh/authorized_keys and paste the contents of the public key)

Some providers also allow you to add the key to the authorized_keys file via a web interface.

Now you need to create the folder where the blog will be deployed (e.g. mkdir -p ~/public_html/blog). For the deployment described here, you also need to create the live folder inside this directory (mkdir -p ~/public_html/blog/live).

The live folder is where the published files of your blog will be located. During deployment, the new files are first copied to a temporary directory (_tmp). Afterwards, the live folder is replaced by the _tmp folder, so the blog is only unavailable for the moment of renaming.

If you want to use your own domain or subdomain (e.g. blog.example.com) for your blog, point it to the live folder.

If your provider allows it, you should create an SSL certificate for the (sub-)domain (with HTTPS redirection).

Configure GitLab

The next step is to provide the private key to the build server so it can authenticate to the target system.

This file should not be part of the repository, as a private SSH key is a sensitive credentials and should never be versioned. Instead, the key is securely provided via GitLab CI/CD environment variables. Access to these variables can be restricted so that even project members with write or merge rights cannot see their contents. This ensures that sensitive data such as the private key remains protected even if external contributors or less privileged users contribute to the project.

  1. Log in to https://gitlab.com/

  2. Go to Settings  CI/CD  Variables in your project

    If you have multiple projects in a group, you don’t have to do these steps for each project individually, but can add the variables for the entire group. Go to your group and then to Settings  CI/CD  Variables

  3. Select "Add Variable"

    • Visibility: Masked and hidden ("Masked" ensures that the contents of the variable do not appear in plain text in the CI log.)

      For GitLab to mask the key in logs, it must not contain line breaks. Otherwise, you will see the following error:

      Unable to create masked variable because:
      
          The value cannot contain the following characters: whitespace characters.

      Therefore, we use additional Base64 encoding in the following steps.

    • Set the Protected flag so the variable is only available in protected branches/tags

      Of course, the branch/tag for which deployment should run must be protected. To do this, go to Settings  Repository  Branch rules in your project and protect the branch there.

    • Description: This setting is optional, but should contain useful information about the purpose of the variable (e.g. Base 64 encoded SSH private key for deployment to <TARGET SERVER>). Ideally, the variable name (key) is already chosen so that a description is no longer necessary.

    • Key: (e.g. SSH_PRIVATE_KEY_BLOG_B64) This is the name of the variable that will be available as an environment variable in your CI/script (here as ${SSH_PRIVATE_KEY_BLOG_B64}). You can choose the name freely (e.g. add a __PROVIDER to indicate the target system, or a _B64 to show that the key is additionally Base64 encoded.)

      Make sure you use the correct variable name in your .gitlab-ci.yml setup.

    • Value: Paste the output of cat ~/.ssh/id_rsa_blog | base64 | tr -d '\n' (including the final =)

      On Linux systems, you can also use cat ~/.ssh/id_rsa_blog | base64 -w 0. On macOS, -w does not work because BSD base64 is used by default. Instead, use base64 | tr -d '\n' to achieve the same result.

      In the CI/CD code, the contents of the variable must be decoded where it is "injected" (for example: echo "${SSH_PRIVATE_KEY_BLOG_B64}" | base64 -d > ~/.ssh/id_rsa). Make sure not to write the decoded content to a log output - otherwise, GitLab cannot mask it. The variant with additional Base64 encoding is just a workaround to prevent you from accidentally logging the content.

  4. Save

Configure GitLab CI

Now you need to specify in your GitLab CI pipeline that the files should be deployed to the target server (using the SSH key), instead of uploading them to GitLab Pages. Make the following adjustments in your .gitlab-ci.yml file.

  1. Remove the comment characters from all lines of the deploy_to_own_server job

    In many editors like VSCode, you can simply select the relevant lines and use a shortcut (in VSCode it’s Ctrl+/) to remove all comment characters. In the same way, you can (if you no longer want to use it) comment out the deploy-to-gitlab-pages job.

  2. Add comment characters to all lines of the deploy_to_gitlab_pages job (or remove the lines completely).

    Of course, you can also use both jobs and (additionally) deploy the data to your own server.

  3. Configure all variables in the "variables" section of the deploy_to_own_server job.

    If you are using a public repository, I recommend storing these variables as GitLab CI environment variables instead of directly in the file in the repository (e.g. as SSH_USER, SSH_HOST, SSH_PORT). You can only mask those variables, if their value has at least 8 characters.

Familiarize yourself with the suggested script and adapt it as needed. There are many ways to perform deployments that are optimized for specific purposes.

  • If you have large files, it may make sense to use rsync to only exchange files that have actually changed.

  • If you have a large number of files, you can speed up the deployment process by first combining the files with tar and then extracting them on the server.

  • You can also consider replacing the files directly in the production system instead of using the _tmp detour I chose. However, I advise against this for two reasons:

    1. It may happen that you e.g. replace the index file on the target before the newly linked article is present on the server.

    2. If the connection breaks during upload, your blog will consist of old and new files.

Currently, no host key verification is enabled in the .gitlab-ci.yml. For content you want to make public, this is fine. However, if you want to upload data to a server that should not be accessible to third parties, you should use host key verification. During deployment, add the public key of the target server to the known_hosts file in the CI pipeline container. This way, you can ensure secure deployment even if there is a risk of MITM (Man-in-the-Middle) attacks.

Run the pipeline

Finally, run the pipeline. Simply commit and push your changes. Afterwards, check whether the job ran successfully / whether the (changed) files are on the server.