Deploying a static website via GitLab CI/CD and Docker to the remote VPS
We’ve all been there - you have your perfect website ready, you bought that awesome domain name and your VPS is ready to host it, but you have no idea how to put it out there in the world… Hopefully this guide will help you out with this common issue most of us faced at some point.
To follow this guide you should have the following prerequisites:
- domain name
- VPS server
- git knowledge
- basic knowledge of GitLab CI/CD
- basic knowledge of Docker/docker-compose
- static (HTML) website
Even if you don’t know what Docker and docker-compose are, you can still follow the guide since deep knowledge of the two is not required and I’m confident that you will be able to complete our goal.
1. Pointing your domain to your VPS
Your VPS will store your website on its storage drive, but how do you reach your VPS by using your domain when your VPS only has a public IP address? DNS to the rescue. Your VPS hosting provider “equipped” you with its own DNS resolver - a handy tool that will help us connect your domain and your VPS server. On the other side, your domain registrar allows you to use custom nameservers. Nameservers will tell your domain where to look for a DNS resolver that will then resolve your domain to the target server’s public IP address - your own VPS. In this example, I will be using InterServer as my VPS provider (with Ubuntu 20.04 installed) and the configuration that I will show will reflect their nameservers/DNS resolver.
First fire up your domain registrar dashboard for the domain that you want to use. Somewhere in the settings, you should find something similar to the picture below (depends on the registrar you are using).
As you can see, we instructed our domain to use InterServer nameservers to hit the domain resolver there.
Now we need to configure the DNS resolver. First login to InterServer (or whoever you picked to be your hosting provider) client area and look for your VPS there. We need to look for the public IP address.
Once we have the IP address of the VPS instance we want to use, we need to instruct our DNS resolver to forward every request that comes from our domain to the target VPS’s IP address. InterServer’s DNS manager can be found in the navigation menu under
More -> DNS manager. We have the option there to input our domain name and the IP address we want to forward (resolve) it to.
Once you are done, hit the
Add DNS Entry button to save the changes. The tricky part here is that DNS resolvers and nameservers can take some time to propagate the changes we just made. To check the status of propagation we can use this site. Input your domain name and run the checks for A record. Once it propagates you should see a lot of green checkmarks. If not, wait for a few minutes and check again until you do.
2. Configuring your VPS
To be able to remotely log in to our VPS (via GitLab), we need to provide our root user (root user is the one you configure along with the VPS upon system installation) with his own SSH key. To do so, we first need to log in to our VPS via ssh inside the terminal/console.
Upon entering the command above you will be prompted to enter the root user’s password. After you log in you should see something like this:
Our remote terminal is ready to take and process new commands. Now we will generate our SSH key. Enter the command:
ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa
Your public key has been saved in /root/.ssh/id_rsa.pub
For each of these prompts, you can just press Enter to leave them blank. Along with the input above, you should also see an image-like fingerprint. So far so good. We now have private/public ssh key pair. Now let’s add our public key to authorized keys by using the command:
cat ~/.ssh/id_rsa.pub > ~/.ssh/authorized_keys
Also, we need to get our private key for later on when GitLab comes along. To get the contents of our private key, use the command:
If everything goes ok you should see terminal output containing a long list of characters like below
-----BEGIN OPENSSH PRIVATE KEY-----
-----END OPENSSH PRIVATE KEY-----
Copy/save it (BEGIN and END line included) as we will need it. Now that we have everything we need, it is time to create a GitLab repository that will hold our website.
3. Configuring GitLab repository
Note: This section assumes that you have basic GitLab CI/CD knowledge. If you are not familiar with it, please refer to this link.
Head over to GitLab and create a new blank project. Project name can be set to your domain name and you might want to set your project to private. Once you create your project, clone it locally to a destination of your choice using the
git clone command:
git clone firstname.lastname@example.org:your.username/repo.name.git
You have your blank repository available locally now. To get CI/CD to work, we need to configure some environment variables. To do so, go to your CI/CD settings page and hit
Expand next to the
Variablessection. Our deployment will need 4 variables set. Configure your variables like in the picture below:
DEPLOY_SERVER_IP - your VPS IP address -> 12.34.567.88
DEPLOY_SERVER_PATH - website path -> /srv/docker/domain.com
DEPLOY_SERVER_USER - should be VPS default user -> root
SSH_PRIVATE_KEY - private key we saved earlier ->
-----BEGIN OPENSSH PRIVATE KEY-----
-----END OPENSSH PRIVATE KEY-----
Make sure to set the masked/protected checkmarks as in the above picture. Once you are done we can move on to creating our local files/folders.
4. Local files
Note: Some knowledge of Docker/docker-compose is required even tho you will be able to understand what is going on even if you don’t have any. If you want to test your website locally, you will have to install Docker and docker-compose to your host OS. If you are not interested in testing locally, skip installing Docker and docker-compose to your OS since they will only be needed on our VPS server.
Navigate to the location where you cloned your GitLab repo earlier in the guide since we will be creating our files there. This folder should be empty and/or contain only the README.md file.
To start, create an empty folder called
html. Inside it create another folder called
localhost if you plan to test locally (read the note above). Create another folder (also inside the
html directory) called
yourdomain.com (replace this with your real domain name). Create a blank file inside the
yourdomain.com folder and name it
index.html. If you already have your website ready, skip the creation of the blank
index.html file and place your website files/folders inside instead. Now
yourdomain.com should consist either of a blank index file or your website files/folders.
Next, we will create a new file in the repository root (outside the
html folder). Name the file
docker-compose.yml. I won’t go into details of the contents for this file because it depends on how familiar you are with docker-compose. Instead, I will just put the contents of the file below for you to copy/paste:
version: '3'services: https-portal:
What does this do? TLDR; This will create an
localhost environment with a self-signed SSL certificate for you (HTTPS). Also, it will run a webserver on your
localhost so that you can test your website (again for the requirements read the note from the start of the section) that is placed inside the
html/localhost directory. If you opted not to test locally you can skip this part.
Local testing requires (as per the note below the section start) that you have Docker and docker-compose installed. While inside the repository root directory (where
docker-compose.yml is), open up your terminal/console and type the following:
After a while, your website should be running on your
locahost webserver and you should be able to access it by using your web browser. Once you get your testing done, you can use
CTRL+C to stop running the webserver/docker container.
You got this far, now let’s do few more steps to get this running in production as well.
Create a new file and name it
docker-compose.prod.yml inside your repository root. The contents of the new file are:
version: '3'services: https-portal:
DOMAINS: 'yourdomain.com, www.yourdomain.com => https://yourdomain.com'
yourdomain.com with your actual domain. This does almost the same as the previous file but this time we are not using localhost, we are using our actual domain. Also, we are redirecting all the traffic from
We don’t want our
localhost testing website to go to our git repository so we should also create
The last file that we need to create is
.gitlab-ci.yml. This is it, the big finale. This file will actually instruct GitLab on how to connect to our VPS and what to do when it does. Put the following inside the
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- eval "$(ssh-agent -s)"
- ssh-add ~/.ssh/id_rsa
- ssh-keyscan -t rsa $DEPLOY_SERVER_IP > ~/.ssh/known_hosts
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
- chmod 644 ~/.ssh/known_hosts
- ssh $DEPLOY_SERVER_USER@$DEPLOY_SERVER_IP "which docker || ( apt-get update -y && apt-get install docker.io -y && systemctl enable --now docker )"
- ssh $DEPLOY_SERVER_USER@$DEPLOY_SERVER_IP "which docker-compose || ( curl -L 'https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)' -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose )"
- ssh $DEPLOY_SERVER_USER@$DEPLOY_SERVER_IP "if [ ! -d "$DEPLOY_SERVER_PATH" ]; then mkdir -p $DEPLOY_SERVER_PATH; fi"
- scp -prq $CI_PROJECT_DIR/* $DEPLOY_SERVER_USER@$DEPLOY_SERVER_IP:$DEPLOY_SERVER_PATH/
- ssh $DEPLOY_SERVER_USER@$DEPLOY_SERVER_IP "cd $DEPLOY_SERVER_PATH && docker-compose -f docker-compose.prod.yml stop"
- ssh $DEPLOY_SERVER_USER@$DEPLOY_SERVER_IP "cd $DEPLOY_SERVER_PATH && docker-compose -f docker-compose.prod.yml up -d"
A lot is going on here so I will try to explain in a way that you can understand even if you don’t have any idea what is going on in here.
We have a stage (
Deploy) and a job inside that stage (
deploy_web). Inside the job, we have 4 tags and they all do their part of the magic. Let’s analyze what each of them does:
stage: Deploy -> Our job belongs to stage called "Deploy"
before_script: -> Set of commands that will fire before the script tag. What we do here is configure our ssh key (private key we added to environment variables earlier in the guide) so that we can connect from GitLab to our target server.
script: -> First we make sure that our VPS target has both Docker and docker-compose installed - if not, we install both of them and run them. Then we make sure that the DEPLOY_SERVER_PATH directory tree exists - if not we must create that also. Afterwards we copy all the contents of our html/yourdomain.com directory to the VPS equivalent so that our "new version" goes live. Then we just restart docker-compose container to reflect the changes we did.
only: - master -> This will tell GitLab not to deploy anything unless the branch is master. If your branch is called main instead, please change this line to reflect it.
Our final repository structure should look something like this:
The only thing that is left now is to actually push your code to the GitLab repository:
git add .
git commit -m "Init commit"
git push origin master
If you use
main instead of
master please correct the last line. If there are no mistakes, you should be able to see the newly created pipeline inside your repository. Once the little blue icon turns to a green checkmark, your website should be up and running. Feel free to head over to
yourdomain.com and verify that everything is as it should be.
Congrats, you just deployed your website via GitLab CI/CD with the help of Docker and docker-compose.
Thanks for reading!