- Rust 55.2%
- Shell 26.6%
- Python 9.6%
- Makefile 3.8%
- HTML 3.2%
- Other 1.6%
| .forgejo/workflows | ||
| dbt-skyduck | ||
| homeassistant | ||
| masiusnl | ||
| nextbytes | ||
| zenbook-duo-kde | ||
| README.md | ||
ByteLabs
Experiment. Iterate. Elevate.
A monorepo showcasing self-hosted projects with fully automated CI/CD via Forgejo Actions.
Every push to main triggers Forgejo Actions pipelines that:
- Flask apps — build and tag Docker images (versioned as
VERSION-SHORTSHA), push to a private container registry, SSH into the deployment server and rundocker compose up -d - Home Assistant — call the HA REST API to pull the latest config from Git and reload all automations, scripts and scenes without a restart
- dbt-Skyduck — run dbt models in a CI environment, retrieve dbt docs artifact, build and publish nginx image and deploy to Portainer to selfhost the dbt docs at
skyduck.masius.nl.
No manual builds. No manual deployments.
Just commit, push, and let the pipeline handle the rest.
Projects
| Project | Description | Port |
|---|---|---|
masiusnl/ |
Minimal Flask app — redirects visitors to a configurable URL | 5299 |
nextbytes/ |
Full Flask website with Traefik reverse-proxy integration | 5099 |
homeassistant/ |
Git-managed Home Assistant config with Forgejo Actions CI/CD | — |
dbt-skyduck/ |
Transavia route network analysis — dbt + DuckDB + OpenSky API | skyduck.masius.nl |
Architecture
Flask apps (masiusnl / nextbytes)
Forgejo push
│
▼
Forgejo Actions workflow
│
├─ Build Docker image
├─ Push to private registry
│
└─ SSH into server
└─ docker compose pull + up -d
Reverse-proxy is handled by Traefik using container labels.
Images are stored in a private container registry (e.g. Forgejo Packages or any OCI-compliant registry).
Home Assistant
Forgejo push
│
▼
Forgejo Actions workflow
│
└─ POST /api/services/shell_command/git_load (HA REST API)
│
└─ git fetch + reset --hard origin/<branch>
│
└─ POST /api/services/homeassistant/reload_all
Config changes are live on HA within seconds of merging to main. Supports manual feature-branch deploys via workflow_dispatch.
dbt-skyduck
Forgejo push
│
▼
Forgejo Actions workflow
│
├─ PR → dbt compile (SQL syntax check, no data needed)
│
└─ main → dbt seed → dbt run → dbt test → dbt docs generate
│
├─ DuckDB in-memory — no infrastructure required
│
└─ Docker build (nginx:alpine + target/ baked in)
│
└─ Push to registry → port 5199 on <your-host-ip>
│
Traefik (separate server) ──┘
routes skyduck.masius.nl via dynamic config
The dbt pipeline and Docker image build both run entirely inside the CI runner.
Repository Structure
bytelabs/
├── .forgejo/
│ └── workflows/
│ ├── build-masiusnl.yml # CI/CD for masiusnl
│ ├── build-nextbytes.yml # CI/CD for nextbytes
│ └── cleanup-old-images.yml # Prune old images from registry (weekly)
├── masiusnl/
│ ├── Dockerfile.prod
│ ├── docker-compose.registry.yml
│ ├── gunicorn_conf.py # Gunicorn config (port 5299)
│ ├── manage.py
│ ├── requirements.txt
│ ├── VERSION
│ ├── .dockerignore
│ └── project/
│ └── __init__.py # Flask app — redirect to REDIRECT_URL
├── nextbytes/
│ ├── Dockerfile.prod
│ ├── docker-compose.registry.yml
│ ├── gunicorn_conf.py # Gunicorn config (port 5099)
│ ├── manage.py
│ ├── requirements.txt
│ ├── Makefile # make dev / make install / etc.
│ ├── pyproject.toml # Poetry dependencies
│ ├── VERSION
│ ├── .dockerignore
│ └── project/
│ ├── __init__.py # Flask app — renders index.html
│ ├── templates/
│ │ └── index.html # Website template
│ └── static/ # CSS, JS, webfonts, images
└── homeassistant/
├── .forgejo/
│ └── workflows/
│ └── deploy-homeassistant.yml # CI/CD — calls HA REST API
├── automations/
│ ├── automation_git.yaml # Pull latest config on HA startup
│ ├── automation_lights.yaml # Sunrise/sunset + presence lights
│ ├── automation_notifications.yaml # Mobile push notifications
│ └── automation_presence.yaml # Device tracker → input_boolean
├── scripts/
│ ├── git_load.sh # git fetch + reset + reload_all
│ └── git_load_wrapper.sh # Sets BRANCH env var, calls git_load.sh
├── configuration.yaml # Main HA config — includes + shell_command
├── groups.yaml
├── scenes.yaml
└── scripts.yaml
└── dbt-skyduck/
├── .forgejo/
│ └── workflows/
│ └── ci-dbt-skyduck.yml # dbt CI + Docker publish
├── docker/
│ ├── nginx.conf # SPA fallback + cache headers
│ └── docker-compose.yml # Traefik labels for skyduck.masius.nl
├── ingest/
│ └── fetch_flights.py # OpenSky API fetcher
├── macros/
│ └── generate_schema_name.sql # clean DuckDB schema names
├── models/
│ ├── staging/ # stg__airports, stg__airlines, stg__routes, stg__opensky_flights
│ ├── intermediate/ # int__transavia_routes_enriched, int__airport_connections
│ └── marts/ # mart__route_frequency, mart__hub_centrality, mart__top_destinations
├── seeds/
│ ├── airports.csv # OpenFlights airports (30 Transavia-relevant airports)
│ ├── airlines.csv # Transavia NL + Transavia France
│ ├── routes.csv # ~63 Transavia routes
│ └── opensky_flights_sample.csv # 3-day flight sample (refresh with make fetch)
├── Dockerfile # nginx:alpine image for skyduck.masius.nl
├── dbt_project.yml
├── profiles.yml # dbt-duckdb (dev + ci targets)
├── packages.yml
├── pyproject.toml
└── Makefile
Required Secrets
Configure the following secrets in your Forgejo/Gitea repository settings:
Flask apps (masiusnl, nextbytes):
| Secret | Description |
|---|---|
REGISTRY_URL |
Hostname of your container registry (e.g. registry.example.com) |
REGISTRY_USERNAME |
Registry login username |
REGISTRY_PASSWORD |
Registry login password / token |
SSH_HOST |
Hostname or IP of the deployment server |
SSH_USER |
SSH username on the deployment server |
SSH_PRIVATE_KEY |
SSH private key (PEM format) |
SSH_PORT |
SSH port (default 22) |
Home Assistant (homeassistant):
| Secret | Description |
|---|---|
HA_TOKEN |
Long-Lived Access Token from HA (Profile → Security → Long-Lived Access Tokens) |
| Variable | Description |
|---|---|
HA_URL |
Full URL of your HA instance (e.g. http://192.168.x.x:8123) — set as a Forgejo repository variable |
dbt-skyduck (dbt-skyduck):
| Secret | Description |
|---|---|
REGISTRY_URL |
Hostname of your container registry |
REGISTRY_USERNAME |
Registry login username |
REGISTRY_PASSWORD |
Registry login password / token |
PORTAINER_WEBHOOK_SKYDUCK |
Portainer stack webhook URL for automatic redeploy |
PORTAINER_API_TOKEN |
Portainer API token (for image pruning) |
PORTAINER_URL |
Portainer base URL (e.g. http://192.168.x.x:9000) |
Required Environment Variables (on server)
Set these where docker compose runs (e.g. a .env file or systemd drop-in):
| Variable | Description | Example |
|---|---|---|
DOMAIN |
Domain name for Traefik routing | example.com |
REGISTRY_URL |
Same as the secret, used in compose file | registry.example.com |
IMAGE_TAG |
Image tag to deploy (injected by CI) | 1.0-abc1234 |
REDIRECT_URL |
(masiusnl only) URL to redirect visitors to | https://www.linkedin.com/ |
Adapting for Your Own Use
Flask apps:
- Replace
myorg/in theIMAGE_NAMEworkflow env vars with your registry org/username - Add all required secrets in Forgejo repository settings
- Set
DOMAINon your deployment server - For
masiusnl: setREDIRECT_URLto your target redirect destination - For
nextbytes: customiseproject/templates/index.htmland add assets toproject/static/
Home Assistant:
- Add the
HA_TOKENsecret in Forgejo repository settings - Add
HA_URLas a Forgejo repository variable (e.g.http://192.168.x.x:8123) - Follow the full setup guide in homeassistant/README.md
dbt-skyduck:
- Add
REGISTRY_URL,REGISTRY_USERNAME,REGISTRY_PASSWORDsecrets in Forgejo repository settings - Update
skyduck.masius.nlindocker/docker-compose.ymlto your own subdomain - Point your Traefik dynamic config at
<your-host-ip>:5199 - See dbt-skyduck/README.md for the full setup and fetch guide
Local Development
Flask apps — both masiusnl/ and nextbytes/ include a Makefile and pyproject.toml for Poetry-based development:
cd nextbytes/ # or masiusnl/
make install # installs dependencies via Poetry
make dev # starts Flask dev server with hot-reload
See each project's own README.md for the full command reference.
Home Assistant — no local dev server; use HA's built-in Developer Tools or the workflow_dispatch trigger to deploy a feature branch directly to your HA instance for testing.
dbt-skyduck — fully local, zero cloud dependencies:
cd dbt-skyduck/
make setup # install deps + dbt packages
make fetch # (optional) pull fresh flight data from OpenSky
make run # seed + run all models
make docs # generate + serve interactive dbt docs
Philosophy
ByteLabs exists to prototype, validate, and refine infrastructure patterns:
- Container-first development
- Automated CI/CD workflows
- Reproducible builds
- Zero-touch deployments
From commit to container — engineered to run anywhere.