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.
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
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