Having used legacy PHP-based CMS solutions such as Wordpress or Typo3 for a long time, I finally switched back to a static website generator, for various reasons. In this article, I want to outline a setup which makes it possible to treat distributed website development as clean as any other software, including versioning and continous deployment.
Why Hugo?
I retired Wordpress in favor of Hugo for several reasons:
- Wordpress has a very old codebase with mixed quality and reliability
- A lot of work was required to make it secure. Actually, my old website was a static copy of a Wordpress backend in order to keep zeroday flaws away
- I would rather prefer to have a static website generated from markup files to avoid lock-in.
- Wordpress supports a little versioning and reviewing. Nice. But, I would prefer the power and comfort of git and gitlab, as I am used to it from daily work.
After evaluating Jekyll, Hugo and Gatsby, I chose Hugo among the three, because:
- Hugo is written in Go. My favourite language.
- Hugo outperforms the other solutions by far
- Hugo feels more stable than Gatsby
- The templating language of Hugo is Golang string templates, which I am pretty familiar with. And, the community templates are a great thing to start, too.
- Hugo follows the UNIX philosophy: Do one thing, and do it well
- No npm depencency spree like in Gatsby
- One binary, very comfortable to setup on a buildserver
Continous Deployment
Having setup a local website, the most important commands are hugo serve
for live development and hugo build
for building a static website. Having a remote server with (s)FTP/Webdav or related access, it would be perfectly viable to just upload the local distribution build (the content of the ./public folder in Hugo) to the remote server, but this might hit scalability issues very soon, for instance if multiple persons are working on the website, or there are requirements with regards to the workflow.
Assuming a remote server with docker, an interesting setup could be:
- Docker with docker-compose (in case of a simple rootserver)
- nginx for serving the stages
- sftp for file access
- lftp for filesync
- Traefik as reverse proxy
- git for versioning
- gitlab-ci for automation and Continous Integration/Deployment
Providing a two-stage (consent and prod) website configuration using docker-compose and traefik:
1ersion: '3'
2
3services:
4 nginx-consent:
5 image: nginx:mainline-alpine
6 restart: always
7 hostname: nginx
8 labels:
9 - "traefik.frontend.rule=Host:$DOMAINNAME_CONSENT"
10 volumes:
11 - ./$DOMAINNAME_CONSENT/$DOMAINNAME_CONSENT:/usr/share/nginx/html
12 environment:
13 - NGINX_HOST=$DOMAINNAME_CONSENT
14
15 nginx-prod:
16 image: nginx:mainline-alpine
17 restart: always
18 hostname: nginx
19 labels:
20 - "traefik.frontend.rule=Host:$DOMAINNAME_PRODUCTION"
21 volumes:
22 - ./$DOMAINNAME_PRODUCTION/$DOMAINNAME_PRODUCTION:/usr/share/nginx/html
23 environment:
24 - NGINX_HOST=$DOMAINNAME_PRODUCTIOn
25
26
27 sftp:
28 image: atmoz/sftp
29 labels:
30 - "traefik.enable=false"
31 volumes:
32 - ./$DOMAINNAME_CONSENT/:/home/$DOMAINNAME_CONSENT
33 - ./$DOMAINNAME_PROD/:/home/$DOMAINNAME_PROD
34 - $VAULT/sftp/ssh_host_ed25519_key:/etc/ssh/ssh_host_ed25519_key
35 - $VAULT/sftp/ssh_host_rsa_key:/etc/ssh/ssh_host_rsa_key
36 - $VAULT/sftp/sftp-users.conf:/etc/sftp/users.conf:ro
37 ports:
38 - "22:22"
When the site is managed by git and a gitlab instance is available, a gitlab-CI script could be:
1variables:
2 DOCKER_DRIVER: overlay2
3 REGISTRY: hub.docker.com
4 GIT_SUBMODULE_STRATEGY: recursive
5
6stages:
7 - build
8 - deploy-consent
9 - deploy-production
10
11build:
12 image: jojomi/hugo:0.58
13 stage: build
14 artifacts:
15 paths:
16 - public
17 script:
18 - hugo
19 - pwd
20 - ls -alsh
21
22deploy-consent:
23 stage: deploy-consent
24 image: mwienk/docker-lftp:latest
25 script:
26 - echo $SFTP_HOST
27 - lftp -e "set sftp:auto-confirm true; mirror -eRv --ignore-time $CI_BUILD_XA $DOMAINNAME_CONSENT; exit;" -u $SFTP_USERNAME,$SFTP_PASSWORD $SFTP_HOST
28 when: manual
29
30deploy-production:
31 stage: deploy-production
32 image: mwienk/docker-lftp:latest
33 script:
34 - echo $SFTP_HOST
35 - lftp -e "set sftp:auto-confirm true; mirror -eRv --ignore-time /$CI_BUILD_XA /$DOMAINNAME_PRODUCTION; exit;" -u $SFTP_USERNAME_PROD,$SFTP_PASSWORD_PROD $SFTP_HOST
36 only:
37 - master
38 - develop
39 when: manual