Files
Notebook/content/blog/migration-gitlab-gitea/index.en.md
Damien 8e9caef392 Fix missing images on English pages
Hugo only auto-shares page-bundle resources across translations when
each language has its own content directory; with filename-based
translation (index.fr.md / index.en.md in the same folder) a resource
with no language code is assigned to the default language only. Add
.en.-suffixed copies of every image referenced from English pages and
point the English markdown at them.
2026-07-18 10:05:47 +02:00

603 lines
16 KiB
Markdown

---
title: "Self-Hosting and Hybrid Cloud: My Personal Infrastructure with Gitea and Scaleway"
date: 2025-11-20
authors:
- name: Damien
link: https://gitea.arnodo.fr/Damien
tags:
- Homelab
- DevOps
- Self-Hosting
- Network Engineering
---
A write-up on building my personal infrastructure: migrating from GitHub to self-hosted Gitea, and deploying ephemeral network labs on Scaleway. All powered by Proxmox and Wireguard.
<!--more-->
## Context and Motivations
As a network engineer working in automation and orchestration, I've always needed an environment to experiment and learn in. Until recently, I used GitHub for my code and ContainerLab locally/on AWS via [DevPod](/documentation/devpod/) for my network simulations.
But several needs emerged:
- **Learning**: Deploying and managing a full Git server with CI/CD
- **Sovereignty**: Hosting my data in France (or at least in Europe), controlling my infrastructure
- **Flexibility**: Being able to spin up network labs on demand without saturating my local machine
- **Automation**: Scripting the complete provisioning of my environments
## The Overall Architecture
![Architecture](global_architecture.en.svg)
### Tech Stack
**Homelab**:
- **Proxmox VE**: Hypervisor for VMs and LXC
- **LXC Containers**: Gitea, runners, various services
- **Ansible**: Configuration management and updates
- **Grafana/Prometheus/Loki**: Monitoring and observability
**Public exposure**:
- **Scaleway Dedibox**: Dedicated instance with a fixed IP
- **Nginx Proxy Manager**: Reverse proxy with automatic SSL
- **Wireguard VPN**: Secure tunnel between Scaleway and the homelab
**Scaleway Cloud**:
- **Object Storage**: Hosting for the Hugo blog (S3-compatible)
- **Instances**: Ephemeral network labs provisioned on demand
- **Scaleway CLI**: Full automation
## Part 1: Migrating from GitHub to Self-Hosted Gitea
### Why Gitea?
[Gitea](https://gitea.io/) is a lightweight Git server, perfect for self-hosting:
- **Lightweight**: Ideal for an LXC on Proxmox
- **GitHub Actions compatible**: Migrate workflows without rewriting them
- **Full-featured**: Issues, PRs, CI/CD, webhooks
- **Open-source**: Active community
### Deployment with Proxmox Helper Scripts
Rather than configuring everything manually, I use the excellent [Proxmox Helper Scripts](https://community-scripts.github.io/ProxmoxVE/), which automate the creation of preconfigured LXCs.
**Installing Gitea**:
```bash
# On the Proxmox node, run the script
bash -c "$(wget -qLO - https://github.com/community-scripts/ProxmoxVE/raw/main/ct/gitea.sh)"
```
The script:
- Creates a Debian 12 LXC
- Installs Gitea and sqlite
- Configures the systemd services
- Sets up the environment with the correct permissions
**Post-installation configuration**:
- Web access: `http://<IP_LXC>:3000`
- Initial setup via the interface
- Site URL: `https://gitea.arnodo.fr`
### Network Architecture: Wireguard + Nginx Proxy Manager
**The problem**: Gitea runs inside my homelab (private IP), but I want to access it from the Internet.
**The solution**:
1. A **Scaleway Dedibox** with a fixed public IP and Nginx Proxy Manager
2. A **Wireguard VPN** between the Dedibox and the homelab
3. The reverse proxy routes `gitea.arnodo.fr` to the LXC's private IP through the tunnel
**Wireguard configuration (homelab side)**:
```ini
[Interface]
Address = 10.0.0.1/24
PrivateKey = <private_key>
ListenPort = 51820
[Peer]
# Dedibox Scaleway
PublicKey = <dedibox_public_key>
AllowedIPs = 10.0.0.2/32
Endpoint = <dedibox_public_ip>:51820
PersistentKeepalive = 25
```
**Nginx Proxy Manager**:
- Proxy Host: `gitea.arnodo.fr`
- Forward Hostname/IP: `10.0.0.x` (Gitea LXC IP over Wireguard)
- Forward Port: `3000`
- SSL: automatic Let's Encrypt
- Websockets: Enabled
### Management with Ansible
To keep Gitea up to date and manage configurations, I use Ansible.
**Update playbook** (`update-gitea.yml`):
```yaml
---
- name: Update Gitea
hosts: gitea
become: yes
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Upgrade Gitea and system packages
apt:
upgrade: dist
autoremove: yes
autoclean: yes
- name: Restart Gitea service
systemd:
name: gitea
state: restarted
enabled: yes
- name: Check Gitea version
command: gitea --version
register: gitea_version
- debug:
msg: "{{ gitea_version.stdout }}"
```
**Execution**:
```bash
ansible-playbook -i inventory.ini update-gitea.yml
```
### Monitoring with Grafana
Gitea exposes Prometheus metrics (https://docs.gitea.com/administration/config-cheat-sheet#metrics-metrics)
Configuration:
**In Gitea (`app.ini`)**:
```ini
[metrics]
ENABLED = true
TOKEN = <secret_token>
```
**Prometheus scrape config**:
```yaml
scrape_configs:
- job_name: 'gitea'
metrics_path: /metrics
bearer_token: '<secret_token>'
static_configs:
- targets: ['<gitea_lxc_ip>:3000']
```
**Grafana dashboard** (https://grafana.com/docs/grafana-cloud/monitor-infrastructure/integrations/integration-reference/integration-gitea/#gitea-integration-for-grafana-cloud):
- Number of repositories, users
- HTTP requests (rate, latency)
- CI/CD runner status
- LXC CPU/RAM usage
### Migrating Code from GitHub
Simple and fast:
Use Gitea's import feature (Settings > New Migration > GitHub), which also migrates issues and releases.
### CI/CD: Deploying Hugo to Scaleway Object Storage
My Hugo blog deploys automatically to Scaleway Object Storage on every push.
**Installing the Gitea Runner** (https://docs.gitea.com/usage/actions/act-runner)
Create the system user for the runner:
```bash
useradd -r -m -d /var/lib/gitea-runner -s /bin/bash gitea-runner
```
Here's a small script that can help install the runner directly inside an LXC:
```bash
sudo apt install -y jq curl tar # si pas déjà
LATEST=$(curl -s 'https://gitea.com/api/v1/repos/gitea/act_runner/releases' | jq -r '.[0].tag_name')
echo "Latest act_runner: $LATEST"
# construire URL de binaire (nommage used: act_runner-<os>-<arch>)
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
# Certains serveurs distribuent binaire sans tar; adapter si archive
URL="https://gitea.com/gitea/act_runner/releases/download/${LATEST}/act_runner-${LATEST#v}-${OS}-${ARCH}"
# essayer télécharger binaire
curl -fL "$URL" -o /tmp/act_runner || {
echo "Téléchargement direct échoué — vérifier le nom exact sur la page release." >&2
exit 1
}
sudo mv /tmp/act_runner /usr/local/bin/act_runner
sudo chmod +x /usr/local/bin/act_runner
```
and to verify:
```bash
/usr/local/bin/act_runner --version
```
>[!NOTE]
> It's important to register the runner so it's recognized by Gitea.
> For more information on configuring the runner, check the official Gitea documentation.
> https://docs.gitea.io/fr/docs/usage/actions/runner/
**Gitea Actions workflow** (`.gitea/workflows/deploy.yml`):
```yaml
name: Build and Deploy Hugo
on:
pull_request:
types: [closed]
branches:
- main
jobs:
build_and_deploy:
if: github.event.pull_request.merged == true
name: Deploy Hugo Website
runs-on: self-hosted
container:
image: debian:bookworm-slim
steps:
- name: Install dependencies
run: |
apt-get update
apt-get install -y git curl ca-certificates wget
- name: Install Hugo
run: |
wget https://github.com/gohugoio/hugo/releases/download/v0.152.2/hugo_extended_withdeploy_0.152.2_linux-amd64.deb -O /tmp/hugo.deb
dpkg -i /tmp/hugo.deb
- name: Checkout code
run: |
git clone --recurse-submodules https://gitea.arnodo.fr/Damien/Notebook.git /tmp/workspace
cd /tmp/workspace
git checkout ${{ github.sha }}
- name: Build site
run: /usr/local/bin/hugo
working-directory: /tmp/workspace
- name: Deploy to Scaleway
run: /usr/local/bin/hugo deploy --force --maxDeletes -1
working-directory: /tmp/workspace
env:
AWS_ACCESS_KEY_ID: ${{ secrets.SCW_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCW_SECRET_KEY }}
```
**Hugo configuration** (`hugo.yaml`):
```yaml
deployment:
targets:
- name: "notebook-arnodo-fr"
URL: "s3://notebook-arnodo-fr?endpoint=https://s3.fr-par.scw.cloud&region=fr-par"
```
**Scaleway Object Storage configuration**:
1. Create a `notebook-arnodo-fr` bucket
2. Enable "Static Website Hosting" mode
3. Generate API credentials (Access Key + Secret Key)
4. Add them as secrets in Gitea (Settings > Secrets > Actions)
**Deployment**:
```bash
git add .
git commit -m "New blog post"
git push origin main
```
The workflow triggers automatically, Hugo builds the site, and deploys it to Scaleway Object Storage. The site is instantly available via the CDN.
## Part 2: Network Labs on Scaleway
### The Problem
Running ContainerLab with several Arista EOS instances locally is:
- **Resource-hungry**: 4-8 GB RAM per cEOS container
- **Local-only**: No access from outside
- **Conflict-prone**: With other Docker/K8s services
### The Solution: On-Demand Scaleway Instances
**Concept**:
- Spin up a Scaleway instance whenever I need a lab
- Automatically install ContainerLab/the VPN via cloud-init
- Destroy the instance after use
- Billed by the hour (< €1 for a few hours of lab time)
### Automation Script: Scaleway CLI
I built a Bash script that manages the entire lifecycle of a lab instance.
**Features**:
- **Creation**: Instance + Security Group (SSH from my IP only)
- **Start/Stop**: Instance management
- **Deletion**: Full cleanup (instance, volumes, IP, SG)
**Script structure** (`scaleway-instance.sh`):
```bash
#!/bin/bash
# Configuration
INSTANCE_NAME="NetLab"
ZONE="fr-par-1"
IMAGE="debian_bookworm"
VOLUME_SIZE=20 # GB
USER_DATA_FILE="user_data.txt"
SECURITY_GROUP_NAME="${INSTANCE_NAME}-SG"
# Détecte l'IP publique actuelle
get_public_ip() {
curl -4 -s ifconfig.me
}
# Crée un Security Group limitant SSH à l'IP publique
create_or_update_security_group() {
PUBLIC_IP=$(get_public_ip)
# Crée le SG avec inbound SSH uniquement depuis PUBLIC_IP/32
# ...
}
# Actions : create, start, stop, delete
case "$1" in
start)
# Démarre l'instance existante
scw instance server start "$INSTANCE_ID" --wait
;;
stop)
# Arrête l'instance
scw instance server stop "$INSTANCE_ID" --wait
;;
delete)
# Supprime instance + volumes + IP + SG
scw instance server terminate "$INSTANCE_ID" --with-ip --with-block
scw instance security-group delete "$SG_ID"
;;
*)
# Crée une nouvelle instance
create_instance "$1" # Type d'instance (DEV1-S, GP1-XS, ...)
;;
esac
```
### Cloud-Init: Automatic Configuration
The `user_data.txt` file contains the cloud-init instructions to automatically provision the instance.
**Example** (`user_data.txt`):
```yaml
#cloud-config
package_update: true
package_upgrade: true
packages:
- git
- curl
- docker.io
- docker-compose
runcmd:
# Installation de ContainerLab
- bash -c "$(curl -sL https://get.containerlab.dev)"
# Clone d'un repo avec des topologies
- git clone https://gitea.arnodo.fr/Damien/network-labs.git /root/labs
# Démarrage d'une topologie par défaut
- cd /root/labs && containerlab deploy -t spine-leaf.clab.yml
```
### Practical Usage
**Create a lab**:
```bash
# Crée une instance DEV1-S avec 20 GB de stockage
./scaleway-instance.sh DEV1-S 20
# Attend quelques minutes pour cloud-init
# Récupère l'IP publique
scw instance server list name=NetLab -o json | jq -r '.servers[0].public_ip.address'
# SSH vers l'instance
ssh root@<IP_PUBLIQUE>
# ContainerLab est déjà lancé !
containerlab inspect
```
**Destroy the lab**:
```bash
./scaleway-instance.sh delete
```
### Raycast Integration
To make things even simpler, I created a Raycast script that lets me manage my instances directly from my Mac.
**Raycast script**:
```bash
#!/bin/bash
# @raycast.schemaVersion 1
# @raycast.title Scaleway Instance
# @raycast.mode silent
# @raycast.icon 🖥️
# @raycast.argument1 { "type": "text", "placeholder": "Action or instance type" }
# @raycast.argument2 { "type": "text", "placeholder": "Volume size", "optional": true }
# @raycast.packageName NetLab
/path/to/scaleway-instance.sh "$1" "$2"
```
**Usage**:
- `⌘ + Space` → "Scaleway Instance DEV1-S" → Creates the instance
- `⌘ + Space` → "Scaleway Instance delete" → Deletes the instance
### Use Case: BGP/EVPN Lab with Arista
**ContainerLab topology** (`spine-leaf.clab.yml`):
```yaml
name: evpn-lab
topology:
nodes:
spine1:
kind: ceos
image: ceos:latest
spine2:
kind: ceos
image: ceos:latest
leaf1:
kind: ceos
image: ceos:latest
leaf2:
kind: ceos
image: ceos:latest
links:
- endpoints: ["spine1:eth1", "leaf1:eth1"]
- endpoints: ["spine1:eth2", "leaf2:eth1"]
- endpoints: ["spine2:eth1", "leaf1:eth2"]
- endpoints: ["spine2:eth2", "leaf2:eth2"]
```
**Workflow**:
1. Create the Scaleway instance
2. Cloud-init deploys the topology
3. Configure BGP/EVPN via Ansible or manually
4. Test, experiment
5. Destroy the instance
**Cost**: DEV1-S instance (2 vCPU, 2GB) = ~€0.015/hour. 4 hours of lab time = €0.06.
## Digital Sovereignty: Why It Matters
This hybrid infrastructure reflects a personal conviction about digital sovereignty.
### The Context
In my work as a network engineer, I see how important it is to be in control of your own infrastructure.
Choosing Scaleway (part of the Iliad group, French) and self-hosting Gitea means:
- **Supporting the European tech ecosystem**
- **Ensuring GDPR compliance**: French jurisdiction
- **Reducing latency**: Datacenters in Paris
- **Understanding**: Owning your entire chain end-to-end
### Learning by Doing
As a network professional (Arista, BGP/EVPN, automation), self-hosting lets me:
- Apply Infrastructure as Code principles
- Deeply understand CI/CD mechanics
- Experiment without limits
- Reproduce professional environments
## Assessment
### What Works Well
**Self-hosted Gitea**:
- Very fast and stable
- Proxmox Helper Scripts = 5-minute install
- Ansible handles updates cleanly
- Grafana monitors everything
**Wireguard + Nginx Proxy Manager**:
- Secure exposure of the homelab
- Excellent performance
- Simple configuration
**Scaleway labs**:
- Provisioning in 3 minutes
- Total flexibility (size, duration)
- Predictable costs (hourly billing)
**CI/CD Hugo → Scaleway Object Storage**:
- Push-to-deploy in 2 minutes
- Free (a few cents/month for storage)
- Built-in CDN = ultra-fast site
### The Challenges
**Initial complexity**:
- Wireguard + reverse proxy = learning curve
- First Proxmox/LXC setup = a few hours
**Maintenance**:
- Responsibility for updates (thankfully Ansible helps!)
- Monitoring to set up yourself
- Backups to automate
**Dependencies**:
- If the Dedibox goes down, Gitea becomes unreachable
- Solution: failover with a second Dedibox or VPS (coming soon)
## Next Steps
- **High availability**: A second Dedibox for failover
- **Automatic backup**: Scripts to back up Gitea to Scaleway Object Storage
- **More automation**: Terraform to provision the entire Scaleway infrastructure
- **Arista MCP**: Building an MCP server to interact with network equipment via local LLMs
- **Netbox integration**: Webhook from Netbox to a network validation pipeline
## Conclusion
This hybrid infrastructure (Proxmox homelab + Scaleway cloud) offers the best of both worlds:
- **Control**: Sensitive data (code, configurations) stays in the homelab
- **Flexibility**: Cloud resources for one-off needs
- **Learning**: A complete environment to experiment in
- **Sovereignty**: Everything hosted in France, with European providers
Self-hosting isn't just about costs (spoiler: I pay about as much as before, if not more), but about learning, mastery, and a deep understanding of systems.
For a network or DevOps engineer, it's the ideal environment to reproduce professional use cases and build up skills.
## Resources
### Documentation
- [Gitea](https://docs.gitea.io/)
- [Proxmox Helper Scripts](https://community-scripts.github.io/ProxmoxVE/)
- [Scaleway CLI](https://www.scaleway.com/en/cli/)
- [ContainerLab](https://containerlab.dev/)
- [Nginx Proxy Manager](https://nginxproxymanager.com/)
### My Repos
- [Blog Hugo](https://gitea.arnodo.fr/Damien/blog)
- [Network Labs](https://gitea.arnodo.fr/Damien/arista-evpn-vxlan-clab) (ContainerLab topologies)
- [Scaleway Scripts](https://gitea.arnodo.fr/Damien/scaleway-automation)
### Community
- [Proxmox Forum](https://forum.proxmox.com/)
- [r/homelab](https://reddit.com/r/homelab)
- [ContainerLab Slack](https://containerlab.dev/)