Writing a blog could be much easier with a blogging platform like WordPress, but then there wouldn’t be a reason to create this post. I prefer writing my blog posts like I write documentation in markdown, versioned and self hosted. There’s no need to have a big platform running just to serve simple static files.

I use hexo, gitlab and rsync in my build process.

Blogging with Hexo

Hexo is a simple, blog-aware static website framework built on top of Node.js.


$ npm install hexo-cli -g

Quick Start

Setup your blog

$ hexo init blog
$ cd blog

Start the server

$ hexo server

Generate static files

$ hexo generate

Versioning and Continuous Deployment with Gitlab

Create repository

Initialize a repository in blog and push your first commit.

git init
git remote add origin [email protected]:<user>/<project_name>.git
git add .
git commit -m "Initial commit"
git push -u origin master

Setup Continuous Deployment

Whenever you push new commits, gitlab looks for a file named .gitlab-ci.yml and fires CI/CD pipelines that build, test and deploy your code. Read rest on Gitlab.

  • Create .gitlab-ci.yml file. And fill like in example:
image: node:latest
    - node_modules/
  - build
  - deploy
job 1:
  stage: build
    - npm install
    - npm run generate
    - public/
job 2:
  stage: deploy
    - sshpass -V
    - sshpass -e rsync -r -a -v -e 'ssh -o StrictHostKeyChecking=no -p '$SSH_PORT --delete public $REMOTE_SERVER:/var/www/html/$DIR
  • Notice that $SSH_PORT, $REMOTE_SERVER, and $DIR are not set. One more variable needed by sshpass is $SSHPASS. To setup environment variables, navigate to your repository Settings > CI / CD.
    Gitlab variables.
  • Git commit and push. Head over to your repository CI / CD > Jobs to check on deployment progress.
    Gitlab pipelines.

Serve static files

I use nginx to serve https://www.amarjanica.com. Configuration is something like:

server {
    listen 80; 
    server_name .<host_name>;
    root /var/www/html/<my_directory>;
    index index.php index.html index.htm;

    location / {

Gitlab Continuous Deployment

Instead of running software installations on each build, you could save your installs and settings to a Dockerfile, and serve the image with Gitlab’s container registry.

Create docker file

Create your docker file in root of your project.

FROM node:latest


RUN apt-get update -qq && apt-get install -y -qq rsync sshpass

Build image

Log in to GitLab’s Container Registry using your GitLab username and password.

docker login registry.gitlab.com

Once you log in, you’re free to create and upload a container image using the common build and push.

docker build -t registry.gitlab.com/<user>/<repository> .
docker push registry.gitlab.com/<user>/<repository>

Reference image in ci

Edit gitlab-ci.yml and reference your docker image:

image: registry.gitlab.com/<user>/<repo>:latest
    - node_modules/
  - build
  - deploy

## Troubleshoot
- ** i/o timeout on docker push  **  
Try changing `/etc/resolv.conf` nameserver to `` and try again.
- **  Permission denied  **  
If you get permission denied when trying to rsync with remote server, then you need to add your user to *www-data* group.
Login to your server and change group ownership of /var/www/html:
``` bash
sudo chgrp -R www-data /var/www/html

Add your user to the www-data group:

sudo gpasswd -a  www-data

Edit file permissions in /var/www/html:

sudo chmod -R 775 /var/www/html

Try creating a file in /var/www/html. If you still get permission denied, you’ll probably need a new login session, or a server restart.

Liked it? Take a second to support Ana Bujan on Patreon!

Notify of
Inline Feedbacks
View all comments
Would love your thoughts, please comment.x