Deploy an Eleventy website using GitLab CI
When choosing a hosting solution for a static website, there are free and very powerful solutions like Netlify or GitHub. The former especially excels in providing very easy and lighting fast solution to deploy a website in matter of seconds.
But what if you want to develop on a good old VPS or metal server, going the self-hosted route? You might already need your server for other applications and use it as well for static content hosting.
Wen you used a few automation tools, there is no way back to that time where we were uploading manually our files using (S)FTP.
Fortunately, you can avoid the chores of manual upload by using a tool such as GitLab CI. This allows you to, for example, trigger a copy of the changed files whenever the master branch is updated. Most of GitLab CI configuration is not done via the GitLab app but rather using a YAML file (it seems most things these days are configured using YAML files).
This is the GitLab CI configuration (.gitlab-ci.yml
) I created to automatically push changes to the server.
stages:
- build
- deploy
eleventy:
stage: build
image: node:lts-buster
cache:
key: "$CI_JOB_NAME"
paths:
- node_modules/
script:
- npm install
- npm run build
artifacts:
paths:
- dist/
only:
- master
rsync:
stage: deploy
image: instrumentisto/rsync-ssh
script:
- mkdir "${HOME}/.ssh"
- echo "${SSH_HOST_KEY}" > "${HOME}/.ssh/known_hosts"
- echo "${SSH_PRIVATE_KEY}" > "${HOME}/.ssh/id_rsa"
- chmod 700 "${HOME}/.ssh/id_rsa"
- rsync -avH dist/* -e ssh ${SSH_USER}@${SSH_HOST}:${WEBROOT_PATH}
only:
- master
There are two main things happening here:
- Eleventy generates the static files.
- The changes are copied to the server.
Defining the order of stages
stages:
- build
- deploy
The block above will tell GitLab CI to execute all jobs from the build stage first, then the deploy stage. You can declare several jobs in the configuration file and assign each of these jobs to a particular stage.
Build stage
The build stage is for installing all node.js dependencies and run Eleventy to generate the static files.
We define a "eleventy" job and assign it to the "build" stage:
eleventy:
stage: build
image
is to attach a specific container image to the job. Here we'll use a long term support node version:
image: node:lts-buster
The cache is really important to make the deploy faster: it allows us to keep the node_modules folder from one deployment to the other. Here we use the job's name as key for the cache, so that only this job retrieves the cache, it will not be used on other cases:
cache:
key: "$CI_JOB_NAME"
paths:
- node_modules/
Now we define the job's work by defining a set of bash instructions to be executed. We install the node modules and run eleventy:
script:
- npm install
- npm run build
Our files are generated, we need to store them somewhere to send them on the server. To accomplish that, GitLab CI provides the "artifacts" system. We can define the files that will be saved in the artifact and reuse them in another job:
artifacts:
paths:
- dist/
Finally, we ask GitLab CI to run this job only when a commit is pushed to the master branch:
only:
- master
Deploy stage
To deploy the files on my server I use a combination of Rsync and SSH. The good news is that there is a Docker container that has these two dependencies installed directly, no need to install them at runtime 😎 So let's declare a new job and, for this one, we'll use this new image:
rsync:
stage: deploy
image: instrumentisto/rsync-ssh
The first part of the job is to create an SSH configuration within the new container:
script:
- mkdir "${HOME}/.ssh"
- echo "${SSH_HOST_KEY}" > "${HOME}/.ssh/known_hosts"
- echo "${SSH_PRIVATE_KEY}" > "${HOME}/.ssh/id_rsa"
- chmod 700 "${HOME}/.ssh/id_rsa"
All of these information are very sensitive, that is why we don't store them directly in the .yml, which will be commited in the repository. Instead we can make use of GitLab CI's environment variables. To define these variables, you can go in your repository's settings on GitLab and add the necessary variables there.
The second part of the script takes care of using rsync to send the changes over SSH:
- rsync -avH dist/* -e ssh ${SSH_USER}@${SSH_HOST}:${WEBROOT_PATH}
The dist
folder will be available to this job, as it was declared as an artifact in the eleventy job.
Finally, we once again ask GitLab CI to run this job only whenever a change is pushed on the master branch:
only:
- master
And... voilà ! Now we can push our changes on master and they will be automatically deployed on the server, nothing more to do! 🎊 🎉
You can find a complete copy of this script on gist:
https://gist.github.com/kant312/da097ad3f91ecc110792df54a95ed82f