Table of Contents
Introductie
Al lang heb ik het idee in mijn hoofd gehad om een blog te starten, maar waar begin je dan? Ik heb namelijk veel kleine projecten waar ik het wel leuk zou vinden om iets over te documenteren. De naam scraplab vind ik dan ook passend bij mijn manier van kennis tot mij nemen. Leren door te doen. Er is echter een bekend gezegde van Adam Savage:
Remember kids, the only difference between screwing around and science is writing it down.
Al snel had ik de essentie van een project te pakken; ik wilde een blog waarbij ik zoveel mogelijk kan automatiseren om het schrijven zo min mogelijk in de weg te zitten. Daarnaast moet het framework ook al het zware werk voor mij doen, zodat ik zoveel mogelijk kan focussen op het schrijven. Het moet ruimte bieden voor pagina’s met plaatjes en om kunnen gaan met markdown, iets wat ik mijzelf heb aangeleerd bij het gebruik van Obsidian.
Eigenlijk alles om er maar voor te zorgen dat ik geen excuus heb om een blogpost te schrijven ;)
Vol goede moed begon ik aan mijn deskresearch, op zoek naar een blogging framework. In eerste instantie kwam ik uit op Hugo, maar daar kwam ik er al vrij snel achter dat de hoeveelheid functionaliteit out-of-the-box toch te miniem zou zijn. Na wat verder te hebben gezocht, ben ik uiteindelijk uitgekomen bij Astro.
Astro heeft standaard alles in huis om een strakke blog neer te zetten. Denk aan statische pagina generatie, schrijven in .md files en niet te vergeten - uitstekende documentatie. Na het lezen van de design principles van Astro zag ik al snel dat dit framework goed bij mijn gestelde eisen past. Op de themes pagina van astro kwam ik Astro Cactus tegen, het thema wat je nu ziet. Een simpel thema met een strakke look, perfect voor de scraps op mijn pagina.
Opzet
De tutorial van Astro zelf heeft al heel veel in petto, maar uiteindelijk heb ik na het volgen van de tutorial ervoor gekozen om een eigen weg in te slaan in plaats van gebruik te maken van Netlify. Dit omdat ik al een VPS heb draaien met daarop een werkende reverse-proxy waarop mijn andere services ook draaien.
Mijn VPS heb ik zo ingericht dat al het binnenkomende verkeer wordt doorgezet naar docker containers die gelabeld zijn met een subdomein van scraplab.nl. Traefik handelt binnenkomende requests op scraplab.nl af en routeert ze naar de juiste docker container. Met een automatisch roterend SSL certificaat via certbot wordt al het verkeer versleuteld en dankzij mijn wildstar DNS record kan ik nu heel makkelijk nieuwe subdomeinen toevoegen aan scraplab.nl. Dit allemaal door simpelweg een nieuwe docker image op te spinnen met een aantal labels. Zo kan ik binnen een handomdraai een nieuw subdomein lanceren.
Docker voor Astro
Dan nu de volgende uitdaging; ik heb Astro lokaal draaiend gekregen en gezien dat het werkt. Nu is het zaak dat ik een Dockerfile bouw die ik vervolgens mijn setup kan gebruiken.
FROM node:lts AS buildWORKDIR /appCOPY . .RUN npm iRUN npm run build
FROM httpd:2.4 AS runtimeCOPY --from=build /app/dist /usr/local/apache2/htdocs/EXPOSE 80
Bovenstaande dockerfile werkt als volgt;
- Hij kopieert de applicatie naar de docker image
- Hij installeert en bouwt Astro middels npm
Vervolgens maak ik een tweede stage genaamd runtime waarin ik:
- met een simpele httpd image de buildfiles kopieer naar de plek waar apache ze verwacht.
EXPOSE 80
is puur documentatie om aan te geven welke poort open moet. Poort 80 (aka HTTP) kan ik hier gebruiken aangezien Traefik automatisch al het binnenkomende verkeer omzet naar 80 voor intern gebruik en zodra het naar buiten gaat weer terugzet naar 443 (aka HTTPS) met behulp van het SSL certificaat.
Met multi-stage builds kan ik gebruik maken van de node:lts
image zonder dit uiteindelijk in mijn uiteindelijke docker image op te nemen. Dit resulteert dan ook in een klein docker image van 57.42 Mb:

Github + Docker Hub
Vervolgens wil ik natuurlijk dat dit image gebouwd wordt op het moment dat ik een wijziging heb aangebracht in mijn blog. Dat doe ik het liefste zodra ik iets commit en push in mijn blog repository.
Ik heb nog niet eerder gewerkt met Github Actions, maar maak wel al jarenlang gebruik van Github. Dit was voor mij dan ook een goed moment om mij hierin eens te verdiepen. Github Actions werkt op basis van configuraties die toegepast worden als bepaalde voorwaarden worden voldaan. Die kun je instellen middels het on:
object in je configuration.yml bestand.
name: Publish Docker image
on: push: branches: ["master"]
Zo zie je hier dat ik een trigger heb geschreven die afwacht totdat er een push plaatsvindt op de master branch.
Vervolgens maak ik dankbaar gebruik van de community, namelijk de github actions die door Docker en Github zelf worden aangeboden:
- https://github.com/actions/checkout
- https://github.com/docker/login-action
- https://github.com/docker/metadata-action
- https://github.com/docker/build-push-action
jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest permissions: packages: write contents: read attestations: write id-token: write steps: - name: Check out the repo uses: actions/checkout@v4
- name: Log in to Docker Hub uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 with: images: scraplab/blog
- name: Build and push Docker image id: push uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 with: context: . file: ./Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }}
Dit zijn gecureerde stappen die veelvuldig getest zijn, zowel in de repository zelf als door andere afnemers. Ik kan middels deze kleine herbruikbare stappen dus al snel een docker image publishen naar Docker Hub!
Mijn workflow is nu bijna compleet, er mist nog maar een laatste stap: de deploy stap.
Deployen maar!
Natuurlijk wil ik na al deze stappen automatisering ook de deployment automatiseren. Ik wist al langer van de docker image Watchtower
maar had er nog geen usecase voor - tot nu.
Watchtower is een docker image die middels de docker socket andere containers letterlijk in de gaten kan houden. Middels environment variabelen en labels op containers kun je aangeven welke containers automatisch geupdate moeten worden.
De standaard configuratie is om alle containers die binnen Docker draaien te updaten naar de laatste versie, maar dat is niet handig voor mijn andere services die hierdoor wellicht omvallen. Denk aan home-assistant of andere gameservers die spontaan beginnen te updaten waardoor ze compatibiliteitsissues krijgen met de clients. Gelukkig hebben de developers van Watchtower hierover nagedacht. Je kan namelijk met WATCHTOWER_LABEL_ENABLE
aangeven dat in plaats van opt-out je een opt-in strategy wilt gebruiken.
Zo kun je met com.centurylinklabs.watchtower.enable=true
als label op een container aangeven dat Watchtower de container in de gaten moet houden.
Standaard scanned Watchtower elke dag 1 keer of de containers geupdate moeten worden. In het geval van mijn blog wil ik dat het gelijk live staat zodra ik heb gepushed. Om die reden heb ik gekozen voor een interval van 1 minuut.
version: '3.7'
services: watchtower: image: containrrr/watchtower:1.7.1 container_name: watchtower volumes: - /var/run/docker.sock:/var/run/docker.sock:ro ports: - 8080:8080 networks: - proxy environment: - WATCHTOWER_LABEL_ENABLE=true - WATCHTOWER_POLL_INTERVAL=60
networks: proxy: external: true
Op basis van Watchtower wordt nu elke minuut gechecked of mijn docker image geupdate is. Als er een nieuwe versie is, dan download Watchtower de laatste versie en start hij de container opnieuw op met dezelfde argumenten. Zo zal mijn server altijd voorzien zijn van de laatste versie van mijn blog.
Conclusie

Door Astro te combineren met mijn bestaande server, is het gelukt om in een weekend een blog op te spinnen en een blog te schrijven. Ik hoop hier meerdere posts te gaan maken over projecten, hoe groot of klein ze ook zijn. Het kan zijn dat dit de laatste is voor een tijdje, maar dan heb ik in ieder geval een goede technische basis gelegd in combinatie met een strakke CI/CD pipeline waardoor ik in ieder geval op dat vlak niet geremd kan worden.
// Brian