So habe ich rdgy.at aufgebaut – und so kannst du es nachmachen:
- Hugo für die Website
- Blowfish als Theme
- Azure Static Web Apps für das Hosting
- OpenTofu für die Infrastruktur
- GitHub Actions für das automatische Deployment
Am Ende sieht die URL-Struktur so aus:
www.example.com/ -> Startseite
www.example.com/blog/ -> Blog
www.example.com/projects/
www.example.com/services/
example.com -> leitet auf https://www.example.com weiterWenn du diesen Aufbau für dein Unternehmen brauchst – ob als einfacher Blog, Unternehmenswebsite oder als eigenständiges Produkt wie Belegmappe.at – melde dich unter office@rdgy.at. Wir besprechen, was du brauchst, und ich sage dir ehrlich, ob und wie ich helfen kann.
Phase 1 – Website lokal aufbauen#
Deutsch ist die Standardsprache. Inhalte entstehen zuerst unter content/de, danach folgt die Übersetzung unter content/en.
1. Werkzeuge installieren#
Du brauchst:
- Hugo Extended
- Git
- OpenTofu
- Azure CLI
Auf macOS reicht ein einziger Befehl:
brew install hugo opentofu azure-cliDanach kurz prüfen, ob alles korrekt installiert ist:
hugo version
tofu version
az version2. Website anlegen und Blowfish einbinden#
hugo new site my-site
cd my-site
git init
git submodule add -b main \
https://github.com/nunocoracao/blowfish.git \
themes/blowfish
mkdir -p config/_default
cp themes/blowfish/config/_default/*.toml config/_default/3. Hugo konfigurieren#
Die zwei wichtigsten Konfigurationsdateien:
config/_default/hugo.toml– allgemeine Einstellungen, Sprachen, Navigationconfig/_default/params.toml– Layout und Aussehen
config/_default/hugo.toml#
theme = "blowfish"
baseURL = "https://www.example.com/"
defaultContentLanguage = "de"
defaultContentLanguageInSubdir = false
enableRobotsTXT = true
summaryLength = 0
[outputs]
home = ["HTML", "RSS", "JSON"]
[taxonomies]
tag = "tags"
category = "categories"
[languages]
[languages.en]
weight = 2
contentDir = "content/en"
[languages.de]
weight = 1
contentDir = "content/de"
[languages.en.menu]
[[languages.en.menu.main]]
name = "Blog"
pageRef = "blog"
weight = 10
[[languages.en.menu.main]]
name = "Projects"
pageRef = "projects"
weight = 20
[[languages.en.menu.main]]
name = "Services"
pageRef = "services"
weight = 30
[languages.de.menu]
[[languages.de.menu.main]]
name = "Blog"
pageRef = "blog"
weight = 10
[[languages.de.menu.main]]
name = "Projekte"
pageRef = "projects"
weight = 20
[[languages.de.menu.main]]
name = "Leistungen"
pageRef = "services"
weight = 30Ein paar wichtige Punkte:
baseURLzeigt immer aufwww- Deutsch liegt direkt auf
/, Englisch unter/en/ - Die Navigation wird hier pro Sprache definiert
config/_default/params.toml#
defaultBackgroundImage = "img/background.jpg"
[homepage]
layout = "background"
homepageImage = "img/background.jpg"
showRecent = true
showRecentItems = 3
[article]
showDate = true
showAuthor = true
showReadingTime = true
showTableOfContents = true
showTaxonomies = true
showPagination = true
showHero = true
heroStyle = "thumbAndBackground"
[list]
showHero = true
heroStyle = "background"
showSummary = true
showCards = true
cardView = trueDas ergibt:
- Startseite mit Hintergrundbild
- Hero-Bereiche auf Blog- und Projektseiten
- Die neuesten Beiträge direkt auf der Startseite
- Kartenansicht für Blog und Projekte
4. Inhaltsstruktur anlegen#
Abschnitte (Blog, Projekte) bekommen eine _index.md. Einzelne Seiten (Artikel, Projekte) sind normale .md-Dateien.
mkdir -p content/de/blog
mkdir -p content/de/projects
mkdir -p content/en/blog
mkdir -p content/en/projects
mkdir -p static/imgDie Struktur sieht dann so aus:
content/
├── de/
│ ├── _index.md
│ ├── blog/
│ │ ├── _index.md
│ │ └── first-post/index.md
│ ├── projects/
│ │ ├── _index.md
│ │ └── product-a.md
│ └── services.md
└── en/
├── _index.md
├── blog/
├── projects/
└── services.mdBeispiel Startseite#
content/de/_index.md
---
title: "Dominic"
description: "Cloud Engineer aus Innsbruck."
---
Ich baue Cloud-Infrastruktur für Teams, die Production-Deployments
lieber in erfahrene Hände legen.Beispiel Blog-Index#
content/de/blog/_index.md
---
title: "Blog"
description: "Notizen zu Cloud, Infrastructure as Code und Software Delivery."
---Beispiel Projektseite#
content/de/projects/product-a.md
---
title: "Produkt A"
description: "Kurze Produktbeschreibung."
summary: "Kurzer Text für die Projektkarte in der Übersicht."
showDate: false
showReadingTime: false
---
## Idee
Worum es im Produkt geht.
## Herausforderungen
- Herausforderung eins
- Herausforderung zwei
## Tech Stack
- Hugo
- PostgreSQL
- Azure
## Live-Projekt
[product-a.com](https://product-a.com)Zuerst die deutschen Inhalte schreiben, dann die Übersetzung unter content/en/ anlegen.
5. Website lokal testen#
hugo serverDann im Browser aufrufen:
http://localhost:1313/– deutsche Versionhttp://localhost:1313/en/– englische Version
Erst wenn die Website lokal so aussieht wie gewünscht, geht es mit dem Deployment weiter.
Phase 2 – Infrastruktur mit OpenTofu#
OpenTofu beschreibt die Azure-Infrastruktur als Code. Einmal anlegen, danach wiederverwendbar und versionierbar.
6. Azure mit OpenTofu provisionieren#
Zuerst ein infra/-Verzeichnis im Projektordner anlegen und die vier Dateien darin erstellen. Danach die Zugangsdaten beschaffen und alles mit tofu apply ausrollen.
mkdir infrainfra/variables.tf#
Hier werden alle Eingabewerte deklariert, die OpenTofu benötigt. Die Werte selbst stehen nicht hier – sie kommen später aus terraform.tfvars. So bleiben Struktur und Geheimnisse sauber getrennt.
sensitive = true sorgt dafür, dass OpenTofu diese Werte niemals im Terminal ausgibt – auch nicht beim apply.
location hat einen Standardwert (westeurope), der verwendet wird, wenn du keinen anderen angibst.
variable "subscription_id" {
type = string
sensitive = true
}
variable "client_id" {
type = string
sensitive = true
}
variable "client_secret" {
type = string
sensitive = true
}
variable "tenant_id" {
type = string
sensitive = true
}
variable "location" {
type = string
default = "westeurope"
}infra/main.tf#
Das ist die eigentliche Infrastruktur. OpenTofu liest diese Datei und legt die beschriebenen Ressourcen in Azure an – oder aktualisiert sie, falls sie schon existieren.
terraform-Block – legt fest, welche Provider-Version verwendet wird. azurerm ist das offizielle Azure-Plugin. Die Version ~> 3.0 erlaubt alle Patch-Updates innerhalb von Version 3, aber keinen Major-Wechsel.
provider "azurerm" – verbindet OpenTofu mit deinem Azure-Account. Die Zugangsdaten kommen aus den Variablen oben.
azurerm_resource_group – eine Resource Group ist ein logischer Container in Azure. Alle zugehörigen Ressourcen landen darin. Einfach löschen, alles weg.
azurerm_static_web_app – das eigentliche Hosting. Azure Static Web Apps stellt die Website aus, verteilt sie global über ein CDN und kümmert sich um HTTPS automatisch. Das Free-Tier reicht für eine normale Website vollständig aus.
azurerm_static_web_app_custom_domain – verknüpft deine eigene Domain (www.example.com) mit der Static Web App. cname-delegation bedeutet: Azure prüft, ob der CNAME-Eintrag im DNS auf die Azure-Adresse zeigt, bevor die Domain aktiviert wird.
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
required_version = ">= 1.6.0"
}
provider "azurerm" {
features {}
subscription_id = var.subscription_id
client_id = var.client_id
client_secret = var.client_secret
tenant_id = var.tenant_id
}
resource "azurerm_resource_group" "site" {
name = "rg-my-site"
location = var.location
}
resource "azurerm_static_web_app" "site" {
name = "stapp-my-site"
resource_group_name = azurerm_resource_group.site.name
location = azurerm_resource_group.site.location
sku_tier = "Free"
sku_size = "Free"
}
resource "azurerm_static_web_app_custom_domain" "www" {
static_web_app_id = azurerm_static_web_app.site.id
domain_name = "www.example.com"
validation_type = "cname-delegation"
}infra/outputs.tf#
Nach dem apply gibt OpenTofu diese Werte im Terminal aus. Du brauchst sie für die nächsten Schritte – Domain und GitHub-Secret.
site_url– die automatisch generierte Azure-URL deiner Website (z.B.https://proud-rock-abc123.azurestaticapps.net). Damit kannst du sofort testen, ob das Deployment funktioniert hat.deployment_token– der geheime Schlüssel, den GitHub Actions braucht, um Dateien nach Azure hochzuladen. Kommt gleich als GitHub-Secret.www_cname_target– der Hostname, auf den deinwww-CNAME-Eintrag beim DNS-Anbieter zeigen muss.
output "site_url" {
value = "https://${azurerm_static_web_app.site.default_host_name}"
}
output "deployment_token" {
value = azurerm_static_web_app.site.api_key
sensitive = true
}
output "www_cname_target" {
value = azurerm_static_web_app.site.default_host_name
}Azure-Zugangsdaten beschaffen#
Bevor du terraform.tfvars befüllst, brauchst du die vier Werte aus Azure. Einmalig einrichten:
1. In Azure einloggen
az loginEin Browserfenster öffnet sich – dort mit dem Microsoft-Konto anmelden.
2. Subscription ID auslesen
az account show --query id -o tsvDiesen Wert als subscription_id eintragen.
3. Service Principal erstellen
Ein Service Principal ist ein technischer Account, den OpenTofu zum Anlegen von Ressourcen in Azure verwendet – ähnlich wie ein API-Key, aber für Azure.
az ad sp create-for-rbac \
--name "sp-my-site" \
--role Contributor \
--scopes /subscriptions/<DEINE_SUBSCRIPTION_ID>Die Ausgabe sieht so aus:
{
"appId": "...", <- das ist client_id
"password": "...", <- das ist client_secret
"tenant": "..." <- das ist tenant_id
}Das Secret wird nur einmal angezeigt – sofort sichern.
infra/terraform.tfvars#
Jetzt die vier Werte eintragen. Diese Datei enthält echte Zugangsdaten – sie darf nie ins Git-Repository. Am besten sofort in .gitignore eintragen:
echo "infra/terraform.tfvars" >> .gitignoresubscription_id = "..."
client_id = "..."
client_secret = "..."
tenant_id = "..."Infrastruktur anlegen#
cd infra
tofu init
tofu apply
cd ..tofu init lädt den Azure-Provider herunter. tofu apply zeigt dir zuerst eine Vorschau aller Ressourcen die angelegt werden – mit yes bestätigen.
Phase 3 – CI/CD mit GitHub Actions#
Ab hier läuft jedes Deployment automatisch: Push auf main → Hugo baut die Website → Azure veröffentlicht sie.
7. GitHub-Repository einrichten#
Falls noch nicht vorhanden, ein leeres Repository auf github.com anlegen. Dann lokal verbinden:
git remote add origin https://github.com/<dein-username>/my-site.git8. GitHub Actions konfigurieren#
.github/workflows/deploy.yml anlegen:
name: Deploy rdgy.at to Azure
on:
push:
branches: [main]
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: '0.157.0'
extended: true
- name: Build
run: hugo --minify
- name: Deploy to Azure Static Web Apps
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
action: "upload"
app_location: "public"
output_location: ""
skip_app_build: trueHugo baut die Website nach public/ – der Deploy-Schritt lädt diesen Ordner direkt nach Azure hoch.
9. GitHub-Secret anlegen#
Das Deployment-Token aus OpenTofu auslesen:
cd infra
tofu output -raw deployment_token
cd ..Diesen Wert als Secret in GitHub hinterlegen:
- Repository → Settings → Secrets and variables → Actions → New repository secret
Name:
AZURE_STATIC_WEB_APPS_API_TOKENPhase 4 – Domain & Live#
10. Domain konfigurieren#
Schritt 1: CNAME-Ziel auslesen
cd infra
tofu output www_cname_target
cd ..Die Ausgabe sieht ungefähr so aus:
proud-rock-abc123.azurestaticapps.netSchritt 2: DNS-Einträge setzen
Beim DNS-Anbieter (z.B. Cloudflare, namecheap, domains.google) folgende zwei Einträge anlegen:
| Typ | Name | Wert |
|---|---|---|
| CNAME | www | proud-rock-abc123.azurestaticapps.net |
| Redirect / ALIAS | @ (Apex) | https://www.example.com |
Der www-Eintrag verbindet deine Domain direkt mit Azure. Den Apex-Eintrag (example.com ohne www) setzt du als HTTP-Weiterleitung – die meisten DNS-Anbieter nennen das „Redirect" oder „URL Forward".
Schritt 3: Domain-Validierung abwarten
Azure prüft automatisch, ob der CNAME-Eintrag korrekt gesetzt ist. Das kann einige Minuten bis zu einer Stunde dauern, je nach DNS-Anbieter. Danach ist HTTPS automatisch aktiv – kein Zertifikat manuell beantragen.
Status prüfen:
az staticwebapp hostname list --name stapp-my-site --resource-group rg-my-siteSobald provisioningState auf Succeeded steht, ist die Domain aktiv.
11. Pushen und live gehen#
git add .
git commit -m "feat: initial site"
git push origin mainGitHub Actions startet automatisch, baut die Website und deployed sie zu Azure Static Web Apps.
Kurzfassung#
| Phase | Was passiert |
|---|---|
| 1 – Lokal | Hugo + Blowfish aufsetzen, Inhalte schreiben, lokal testen |
| 2 – IaC | Azure-Infrastruktur mit OpenTofu anlegen |
| 3 – CI/CD | GitHub Actions für automatisches Deployment einrichten |
| 4 – Live | Domain konfigurieren, ersten Push machen |
Das Ergebnis ist eine schnelle, statische Website mit Blog, Projekten und zwei Sprachen – ohne eigenen Server.
