Inhaltsverzeichnis
- Warum „einfach lokal laufen lassen" bei uns nicht funktioniert
- Unser Setup: Ein Cluster, viele Entwickler
- Die Architektur
- Wie wir wirklich arbeiten: Ein echtes Szenario
- Traffic Matching Optionen
- Warum WireGuard
- Wie wir mit Shared State umgehen
- Team Setup
- Was wir gelernt haben
- Wann wir das nicht nutzen
- Von Isolation zu Partizipation
- Kurzreferenz
Über den Autor
09.04.2026
Wie wir Gefyra im Alltag nutzenHandeln statt Isolieren: Wie wir bei Blueshoe mit Kubernetes entwickeln
Lokal entwickeln, im Cluster testen - ohne CI/CD-Wartezeiten. Gefyra verspricht den Heiligen Gral der Kubernetes-Entwicklung: Full-Speed-Coding mit echtem Cluster-Kontext. In unserem Praxisbericht erfährst du, warum das Tool ein Gamechanger für dein Daily Business ist und wie du es effizient in deinen Workflow integrierst
Γέφυρα (Gefyra) - altgriechisch für „Brücke". Ortega y Gasset schrieb „Yo soy yo y mi circunstancia" - Ich bin ich und meine Umstände. Code existiert nicht in Isolation, genauso wenig wie Identität. Er entsteht aus seiner Umgebung. So haben wir aufgehört zu simulieren und angefangen teilzunehmen.
Warum „einfach lokal laufen lassen" bei uns nicht funktioniert
In der Containerisierung steckt ein Versprechen: „Einmal bauen, überall laufen lassen." Docker sollte die „Läuft auf meinem Rechner"-Ära beenden. Für einfache Anwendungen hat das geklappt. Aber irgendwo zwischen einem einzelnen Container und den Systemen, die wir tatsächlich für Kunden bauen, ist dieses Versprechen leise zerbrochen.
Ein typisches Kundenprojekt bei Blueshoe: Django, Celery mit mehreren Worker-Typen, PostgreSQL, Redis, manchmal Elasticsearch, ein Nginx-Reverse-Proxy, und mindestens eine externe Integration — ein Payment Provider, ein ERP-System, eine Versand-API — die nur Traffic von freigeschalteten IPs akzeptiert.
So sieht „das lokal laufen lassen" dann wirklich aus:
Unsere lokale Entwicklungsrealität (vorher):
┌───────────────────────────────────────────────────────────────┐
│ Entwickler-MacBook (16GB RAM) │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Django │ │ Celery │ │ Celery │ │ Celery │ │ Nginx │ │
│ │ App │ │ Default │ │ Email │ │ Heavy │ │ Proxy │ │
│ │ 800MB │ │ 400MB │ │ 400MB │ │ 600MB │ │ 100MB │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Postgres │ │ Redis │ │ Elastic │ │LocalSt. │ ... │
│ │ 1GB │ │ 200MB │ │ 2GB │ │ Mock │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ Summe: ~6-8GB RAM, Lüfter auf Anschlag, Laptop als Heizung │
│ │
│ Und trotzdem: │
│ ✗ Payment-API lehnt Requests ab (IP nicht gewhitelistet) │
│ ✗ Kein Service Mesh (Istio-Verhalten unsichtbar) │
│ ✗ Network Policies existieren nicht │
│ ✗ Secrets sind veraltete .env-Dateien │
│ ✗ Datenbank hat Testdaten, keine echten Edge Cases │
└───────────────────────────────────────────────────────────────┘
Die Isolation, die Docker nützlich macht, wird zum Problem. Unsere lokalen Umgebungen sind hermetisch von der Realität abgeriegelt. Die Datenbank hat nicht die Daten, die den Bug auslösen. Der gemockte Payment Provider macht keine Timeouts wie der echte. Das Single-Node-Setup zeigt nicht die Netzwerkprobleme, die wir in Production sehen.
Wir haben gegen eine Fiktion entwickelt. Eine nützliche Fiktion manchmal — aber eben eine Fiktion.
Das Workload-Problem
Unsere Kundenprojekte sind keine simplen CRUD-Apps. Da steckt mehr drin:
- Mehrere Worker-Typen — Celery-Queues mit unterschiedlichen Concurrency-Settings, unterschiedlichen Resource Limits, unterschiedlichem Verhalten unter Last. Die Default-Queue macht schnelle Tasks; die Heavy-Queue verarbeitet große File-Uploads mit anderen Memory-Constraints. Lokal laufen die alle gleich.
- Background Jobs — Scheduled Tasks, die dieselben Daten anfassen wie unsere Request Handler. Ein nächtlicher Aggregation-Job, der erst abbricht, wenn er auf sechs Monate angesammelte Daten trifft. Ein Webhook-Processor, der nur versagt, wenn drei gleichzeitig reinkommen.
- Service-Mesh-Sidecars — Istio injiziert mTLS, Retries, Circuit Breaker, Traffic Splitting. Verhalten, das wir lokal weder sehen noch nachstellen können, weil es kein Mesh gibt, in das injiziert werden könnte.
- Init Container — Migrations, Cache Warming, Dependency Checks, die vor dem App-Start laufen. Lokal überspringen wir die oder führen sie manuell aus. Im Cluster gehören sie zum Startup-Vertrag.
- Network Policies — Traffic-Einschränkungen, die auf einem Laptop schlicht nicht existieren. Das Backend darf nur mit der Datenbank reden, nicht direkt mit Redis. Der Worker erreicht die externe API, der Web-Pod nicht. Diese Constraints prägen unsere Architektur, aber in der lokalen Entwicklung sind sie unsichtbar.
- Secrets Management — Vault-Integrationen, External Secrets Operators, Sealed Secrets. Unser lokales Docker Compose weiß davon nichts. Wir kopieren Werte in
.env-Dateien und hoffen, dass sie noch aktuell sind.
Jeder dieser Punkte ist ein Umstand, von dem unser Code abhängt. Jeder fehlt im lokalen Setup. Jeder ist eine Quelle für Bugs, die erst „in der echten Umgebung" auftreten — also vor Kunden, oder schlimmer, in Production.
Wir kennen das alle: Der Code läuft perfekt auf dem eigenen Rechner, besteht die CI, wird erfolgreich deployed — und fällt dann im Staging auf die Nase wegen irgendwas, das man lokal unmöglich hätte testen können. Ein Timeout. Eine Network Policy. Eine Race Condition, die nur unter realistischer Last auftritt. Die Umstände waren falsch, also war der Code falsch, obwohl er richtig aussah.
Das Feedback-Loop-Problem
Die Alternative, die wir versucht haben: CI-getriebene ephemere Environments. Code pushen, auf die Pipeline warten, frisches Environment hochfahren lassen, dort testen.
Funktioniert. Ist korrekt. Das Environment entspricht der Realität, weil es die Realität ist — ein frisches Deployment des ganzen Stacks.
Ist aber auch langsam.
Fünfzehn Minuten Feedback-Loop für eine einzeilige Änderung. Warten bis die Pipeline anspringt. Warten bis die Images gebaut sind. Warten bis das Environment provisioniert ist. Warten bis die Pods ready sind. Warten bis die Health Checks durch sind. Dann endlich die Änderung testen.
Multiplizier das mit den Dutzenden kleinen Iterationen, die echtes Debugging braucht. „Liegt's an dieser Zeile? Nein. An der? Nein. Was wenn ich diese Variable logge? Immer noch unklar. Ich probier mal..."
Das ist keine Entwicklung. Das ist Briefverkehr — wir schicken Nachrichten ab und warten auf Antwort. Das Gespräch zwischen Entwickler und Code, das eigentlich unmittelbar und flüssig sein sollte, wurde stockend und zäh.
Wir haben Optimierungen versucht. Schnellere CI-Runner. Gecachte Base Images. Parallele Jobs. Hat am Rand geholfen. Aber das eigentliche Problem blieb: Wir haben Code an einen entfernten Ort gepusht und gewartet, dass er uns sagt was passiert ist. Die Lücke zwischen Schreiben und Wissen war immer noch zu groß.
Wir brauchten was anderes. Keine bessere Simulation. Keine schnellere Pipeline. Eine Brücke zwischen dem Ort, wo wir Code schreiben, und dem Ort, wo er tatsächlich läuft.
Also haben wir eine gebaut.
Unser Setup: Ein Cluster, viele Entwickler
Das Fundament unseres Workflows ist ein gemeinsamer Development-Cluster. Nicht ein Cluster pro Entwickler. Nicht ein Cluster pro Feature Branch. Ein Cluster, der den kanonischen Zustand der Anwendung hält.
Das ist eine philosophische Entscheidung genauso wie eine praktische. Statt dass jeder Entwickler eine Miniaturfiktion auf seinem Laptop mit sich trägt, pflegen wir eine gemeinsame Realität.
Das traditionelle Modell geht davon aus, dass Isolation ein Feature ist: Deine Umgebung gehört dir, abgeschottet, unfähig andere zu stören. Aber diese Isolation ist auch eine Lüge. Dein lokales Postgres hat keine echten Daten. Dein gemocktes Stripe gibt keine realistischen Fehler zurück. Dein Docker-Netzwerk hat nicht die Latenz einer echten VPC. Du bist vor Störungen geschützt, aber auch vor der Wahrheit.
Unser Cluster läuft mit der Wahrheit. Dieselben Container Images, die ins Staging gehen. Dieselben Network Policies. Dasselbe Service Mesh. Wenn sich einer unserer Engineers verbindet, simuliert er keine produktionsähnliche Umgebung — er nimmt an einer teil.
Wie wir das organisieren
Jedes Kundenprojekt bekommt seinen eigenen Namespace (oder mehrere für größere Systeme). Unsere CI/CD-Pipeline hält diese mit dem aktuellen stabilen Build vom Main Branch befüllt — das ist der „Base State", die Version der Anwendung, die funktioniert.
Wenn einer unserer Engineers an einem Feature arbeitet, erstellt er keine neue isolierte Welt. Er verbindet sich mit der bestehenden. Der Cluster ist der gemeinsame Kontext. Gefyra ist der Mechanismus, der es mehreren Entwicklern erlaubt, in diesem Kontext zu arbeiten ohne sich gegenseitig in die Quere zu kommen — dank Personal Bridges und Shadow Workloads.
Unsere Namespace-Strategie:
┌───────────────────────────────────────────────────────────────┐
│ Kubernetes Development Cluster │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ client-a-dev │ │ client-b-dev │ │ internal-dev │ │
│ │ │ │ │ │ │ │
│ │ • Django App │ │ • FastAPI │ │ • Unsere Tools │ │
│ │ • Celery Worker │ │ • PostgreSQL │ │ • Experimente │ │
│ │ • Redis │ │ • Redis │ │ │ │
│ │ • PostgreSQL │ │ • Externe APIs │ │ │ │
│ │ │ │ │ │ │ │
│ │ Alice & Bob │ │ Carol bridged │ │ │ │
│ │ bridgen hier │ │ die API hier │ │ │ │
│ │ gleichzeitig │ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ CI/CD pflegt den Base State │ Entwickler connecten zum │
│ │ Arbeiten │
└───────────────────────────────────────────────────────────────┘
Die Schlüsselerkenntnis: Der Cluster ist nicht das Ziel für unseren Code. Er ist der Ausgangspunkt für unsere Entwicklung. Wir arbeiten in der Realität, nicht auf sie zu. Der Code kommt gewissermaßen zu uns — die Umgebung ist schon da, wartet, und wir bridgen rein statt sie nachzubauen.
Das verändert wie wir über Entwicklung denken. Wir bauen nicht lokal was und hoffen dass es remote funktioniert. Wir arbeiten remote mit lokalen Tools. Der Unterschied ist wichtig.
Die Architektur
Gefyra ist die Brücke, die diese Verbindung möglich macht:
KUBERNETES CLUSTER
┌───────────────────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ client-project-namespace │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ backend │ │ celery │ │ redis │ │ │
│ │ │ Pod │ │ worker │ │ │ ... │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ ┌─────────┐ │ └─────────────┘ └─────────────┘ │ │
│ │ │ │CARRIER2 │ │ ◄── Rust-Proxy, routet Traffic nach Matching Rules │ │
│ │ │ └────┬────┘ │ │ │
│ │ └──────┼──────┘ │ │
│ │ │ │ │
│ │ ┌──────┴──────┐ │ │
│ │ │ SHADOW │ ◄── Duplikat-Workload, fängt ungematchten Traffic │ │
│ │ │ WORKLOAD │ auf (Original Image, Cluster Upstream) │ │
│ │ └─────────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────┐ │
│ │ STOWAWAY │ ◄── WireGuard-Server, managed Developer- │
│ │ (Deployment) │ Connections via NodePort oder LoadBalancer │
│ └────────┬─────────┘ │
│ │ │
└───────────┼───────────────────────────────────────────────────────────────┘
│
│ ════════════════════════════════════════════
│ WireGuard Tunnel
│ • UDP, Curve25519-Verschlüsselung
│ • Single Round-Trip Handshake
│ • Natives Roaming (überlebt Netzwerkwechsel)
│ ════════════════════════════════════════════
│
┌───────────┼────────────────────────────────────────────────────────────────┐
│ │ │
│ ┌────────┴─────────┐ │
│ │ CARGO │ ◄── WireGuard-Client + DNS-Resolver │
│ │ (Docker Container)│ Macht Cluster Services lokal erreichbar │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ LOKALER CONTAINER │ │
│ │ │ │
│ │ Unser Code läuft lokal, aber: │ │
│ │ • Löst Cluster-DNS auf (postgres.namespace.svc.cluster.local) │ │
│ │ • Verbindet sich mit echten Datenbanken, Caches, Queues │ │
│ │ • Ausgehender Traffic geht übers Cluster-NAT (IP-Allowlists gehen)│ │
│ │ • Hat echte Env Vars, kopiert von laufenden Pods │ │
│ │ • Source gemountet für Hot Reload │ │
│ │ • Debugger via IDE angehängt │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ENTWICKLER-RECHNER │
└────────────────────────────────────────────────────────────────────────────┘
Fünf Komponenten arbeiten zusammen:
Stowaway lebt im Cluster als Deployment, typischerweise via LoadBalancer oder NodePort exponiert. Das ist der WireGuard-Server, der Developer-Connections annimmt, kryptografische Identitäten verwaltet und Traffic routet. Beim Verbinden wird eine GefyraClient Custom Resource angelegt — deine eindeutige Identität mit eigenem Subnet. Mehrere Entwickler können sich gleichzeitig verbinden; jeder bekommt sein eigenes kryptografisches Keypair und seinen IP-Bereich, Traffic bleibt also isoliert obwohl alle denselben Tunnel-Endpoint nutzen.
Cargo läuft auf deinem Rechner als Docker Container. Es hält den WireGuard-Tunnel aufrecht und — das ist entscheidend — fungiert als DNS-Resolver für Cluster Service Names. Deshalb funktioniert curl postgres.client-namespace.svc.cluster.local im Terminal nach dem Connect — Cargo fängt DNS-Queries ab und löst sie über das CoreDNS des Clusters auf. Außerdem kümmert es sich um die Routing-Table-Magie, die Cluster-IPs vom lokalen Netzwerk-Stack aus erreichbar macht.
Carrier2 ist der Rust-basierte Reverse Proxy, gebaut auf Pingora (Cloudflares Proxy-Framework). Wenn du einen Bridge Mount erstellst, patcht der Operator den Ziel-Pod und injiziert Carrier2 neben deiner Anwendung. Carrier2 routet Traffic nach Matching Rules — Header, Pfade, oder Kombinationen — und leitet Requests entweder zu deinem lokalen Rechner oder zum Shadow Workload. Es unterstützt mehrere Ports gleichzeitig und lässt sich graceful via SIGHUP rekonfigurieren ohne Connections zu droppen.
Shadow Workloads lösen das „Was passiert mit Traffic den ich nicht will"-Problem. Wenn du einen GefyraBridgeMount erstellst, dupliziert Gefyra den Ziel-Workload (z.B. deployment/backend wird zu deployment/backend-gefyra). Dieser Shadow läuft mit dem Original Image und dient als „Cluster Upstream" — Traffic der nicht zu deinen persönlichen Routing Rules passt, geht dahin. Nichts geht verloren. Andere Entwickler, automatisierte Tests und Monitoring arbeiten weiter gegen den Shadow, während du nur die Requests abfängst die dich interessieren.
Der Operator orchestriert alles. Ein Standard-Kubernetes-Operator, der Gefyra Custom Resources watched (GefyraClient, GefyraBridgeMount, GefyraBridge) und den Cluster State entsprechend reconciled. Er managed den Lifecycle: Stowaway installieren, Client-Identitäten erstellen, Mount State Machines verwalten (REQUESTED → PREPARING → INSTALLING → ACTIVE → RESTORING → TERMINATED), Pods patchen um Carrier2 zu injizieren, Shadow Workloads erstellen, und aufräumen wenn Entwickler sich disconnecten. Validating Webhooks stellen Konsistenz sicher — nur ein Mount pro Workload, Bridges müssen aktive Mounts referenzieren, immutable Fields bleiben immutable.
Das Ergebnis: Unsere Laptops werden Teil des Cluster-Netzwerks. Unser Code läuft lokal, existiert aber in den Umständen des Clusters. Wir kriegen die schnelle Iteration lokaler Entwicklung mit der Genauigkeit der echten Umgebung.
Wie wir wirklich arbeiten: Ein echtes Szenario
Gehen wir ein Szenario vom letzten Monat im Detail durch.
Alice ist eine unserer Backend-Engineers und arbeitet an einer Payment-Integration für einen Kunden. Der Zahlungsanbieter ist ein europäisches Fintech mit strengen Security-Anforderungen — sie akzeptieren API-Requests nur von einer Whitelist von IP-Adressen, und das NAT-Gateway unseres Clusters steht drauf. Unsere Büro-IP nicht. Alices Heim-IP erst recht nicht.
Bob baut den Frontend-Checkout-Flow, der Alices Endpoint konsumiert. Beide arbeiten im selben Namespace, am selben Feature, Demo ist Freitag.
Schritt 1: Verbindung herstellen
Alice öffnet ihr Terminal. Das hat sie schon hundertmal gemacht:
gefyra connection create -f alice-client.yaml
In Teams ist der Operator vorinstalliert und Admins verteilen Client-Config-Dateien an die Entwickler. Das gefyra up-Kommando gibt es als Convenience-Shortcut — installiert den Operator, erstellt einen Client und connected in einem Rutsch. Perfekt für einzelne Entwickler oder schnelle Experimente. Aber im Team-Setup ist connect das was man täglich nutzt.
Unter der Haube passiert einiges:
- Cargo startet lokal, bekommt die Peer-Konfiguration und baut den WireGuard-Tunnel auf.
- Stowaway akzeptiert die Connection und aktiviert Alices
GefyraClient-Resource. - Traffic Routing wird zwischen Alices Rechner und dem Cluster-Netzwerk etabliert.
Nach etwa fünf Sekunden sieht Alice:
Gefyra client connected.
You can now run containers that have access to the Kubernetes cluster.
Ihr Laptop ist jetzt im Cluster-Netzwerk erreichbar. Schnell verifiziert:
# Cluster-DNS-Auflösung funktioniert vom Terminal aus
$ nslookup redis.client-project.svc.cluster.local
Server: 172.17.0.2
Address: 172.17.0.2#53
Name: redis.client-project.svc.cluster.local
Address: 10.100.47.93
Noch keine Container am Laufen. Kein Code deployed. Nur Connectivity. Ihr Rechner ist jetzt, aus Netzwerk-Perspektive, in der VPC des Clusters.
Schritt 2: Lokal ausführen mit Cluster-Kontext
Jetzt startet Alice ihren Development Container:
gefyra run \
-i blueshoe/client-backend:dev \
-N alice-backend \
-n client-project \
--env-from deployment/backend/api \
-v $(pwd)/src:/app/src \
-p 8000:8000
Was machen die Flags:
| Flag | Was es tut |
|---|---|
-i blueshoe/client-backend:dev | Nutzt unser Dev-Image mit Debug-Tools, ipdb, und Dev-Dependencies die im Production-Image nicht drin sind |
-N alice-backend | Nennt den Container alice-backend damit wir ihn später beim Bridgen referenzieren können |
-n client-project | Zielt auf den client-project-Namespace im Cluster |
--env-from deployment/backend/api | Kopiert alle Environment Variables vom laufenden api-Container im Backend-Deployment |
-v $(pwd)/src:/app/src | Mountet den lokalen Source Code in den Container für Hot Reload |
-p 8000:8000 | Exponiert Port 8000 damit sie den Debugger anhängen kann |
Das --env-from-Flag ist zentral für unseren Workflow. Es greift in den Cluster, findet das angegebene Deployment und den Container, extrahiert alle Environment Variables, und injiziert sie in den lokalen Container. Das beinhaltet:
- Database Connection Strings (zeigen auf das Postgres im Cluster)
- Redis URLs (zeigen auf das Redis im Cluster)
- API Keys und Secrets (die echten, aus Vault oder woher auch immer)
- Feature Flags (die aktuelle Config, keine veraltete Kopie)
- Die Payment-Provider-Credentials (die Alice für ihre Integration braucht)
Keine .env-Datei von vor drei Wochen die jemand vergessen hat zu updaten. Keine docker-compose.override.yml mit hardgecodeten Werten. Die tatsächliche, aktuelle, live Konfiguration die das laufende System nutzt.
Alices Container startet. Djangos Development Server fährt hoch mit aktiviertem Hot Reload. Aber anders als bei normaler lokaler Entwicklung: wenn ihr Code sich mit postgres.client-project.svc.cluster.local verbinden will, klappt das. Das DNS wird durch Cargo zum CoreDNS des Clusters aufgelöst, und die TCP-Connection routet durch den WireGuard-Tunnel zum echten PostgreSQL-Pod.
Wenn ihr Code einen Celery Task einreiht, geht der zum echten Redis Broker. Wenn der Task ausgeführt wird, läuft er auf den echten Celery Workern im Cluster.
Und entscheidend — wenn ihr Code die API des Payment Providers aufruft, geht der HTTP-Request durchs NAT-Gateway des Clusters raus. Die IP steht auf der Allowlist. Die Integration funktioniert wirklich.
Für etwa 80% unserer Entwicklungsarbeit ist das alles was wir brauchen. Connect, run, direkt gegen den Cluster arbeiten. Kein Bridge nötig. Alice kann ihren lokalen Endpoint direkt aufrufen (curl localhost:8000/api/payments/), Management Commands gegen echte Daten laufen lassen, mit voller IDE-Integration debuggen, und Änderungen sofort via Hot Reload sehen.
Schritt 3: Traffic bridgen mit persönlichem Routing
Aber Alice und Bob müssen die Integration end-to-end testen. Bobs Frontend muss Alices Backend aufrufen — und Alices Änderungen sind noch nicht deployed. Hier kommen Personal Bridges ins Spiel.
Zuerst erstellt Alice einen Bridge Mount um die Infrastruktur vorzubereiten:
gefyra mount create \
--target deployment/backend/api \
--namespace client-project \
--name backend-mount
Das triggert eine State Machine:
REQUESTED → PREPARING → INSTALLING → ACTIVE
Hinter den Kulissen:
- Gefyra erstellt einen Shadow Workload (
deployment/backend-gefyra) mit dem Original Image - Der Original-Pod wird mit injiziertem Carrier2 gepatcht
- Carrier2 startet, routet aber noch allen Traffic zum Shadow (keine Bridges bisher)
Der Mount ist jetzt ACTIVE. Traffic fließt weiter normal — alles geht zum Shadow, der sich exakt wie das Original-Deployment verhält.
Jetzt erstellt Alice ihre persönliche Bridge mit Traffic-Matching-Rules:
gefyra bridge create \
--mount backend-mount \
--local alice-backend \
--ports 8000:8000 \
--match-header-exact x-gefyra:alice
Das ist der Moment der Substitution — aber nur für Alices Traffic. Carrier2 rekonfiguriert sich (graceful, via SIGHUP):
- Requests mit Header
x-gefyra: alice→ Alices Laptop via Tunnel - Alle anderen Requests → Shadow Workload (Cluster Upstream)
Aus Sicht jeder anderen Komponente im Cluster hat sich nichts geändert. Der backend-Service löst immer noch zur selben Pod-IP auf. Requests auf Port 8000 bekommen immer noch Responses. Der Ingress Controller routet Traffic wie vorher. Celery Worker rufen die Backend-API wie vorher auf.
Aber jetzt reisen Requests mit Alices Header durch den Tunnel zu ihrem Laptop. Ihr lokaler Django-Prozess verarbeitet sie. Ihre Breakpoints feuern. Ihr Hot Reload funktioniert. Ihre Log-Statements tauchen in ihrem Terminal auf.
Bob konfiguriert seinen Frontend Dev Server so dass er den Header mitschickt:
// In Bobs Frontend-Dev-Config
const headers = { 'x-gefyra': 'alice' };
Die Bridge steht — und sie ist persönlich für Alice.
Schritt 4: Der Moment wo es sich auszahlt
Mittwochnachmittag. Bob hat die Checkout-UI gebaut. Er hat Alices Endpoint eingebunden, das Formular sieht gut aus, er testet den Flow.
Er gibt Testkartendaten ein. Klickt „Bezahlen". Die Seite zeigt einen Fehler: „422 Unprocessable Entity."
In unserem alten Workflow wäre das so gelaufen:
- Bob macht einen Screenshot, postet ihn in Slack
- „Hey Alice, kriege 422 beim Checkout"
- Alice: „Was für einen Request schickst du?"
- Bob öffnet DevTools, kopiert den Request Payload, pastet ihn
- Alice versucht das mit curl nachzustellen. Bei ihr geht's.
- „Kannst du mal die genauen Header checken?"
- Zwanzig Minuten Hin und Her
- Schließlich: „Oh, du schickst die Payment Method als String, nicht als Object"
- Diskussion wessen Code falsch ist
- Nochmal zwanzig Minuten
So lief es wirklich:
Bob schickt Alice die URL zum Dev Environment in Slack: https://dev.client-project.blueshoe.io/checkout. Sie öffnet sie im Browser. Sie schaut auf dieselbe Seite wie Bob.
Sie öffnet PyCharm, findet den Payment-Endpoint-Handler, setzt einen Breakpoint auf die erste Zeile.
„Okay, Seite neu laden und nochmal absenden."
Bob lädt neu. Füllt das Formular aus. Klickt „Bezahlen."
Der Request fließt:
- Von Bobs Browser zum Ingress
- Vom Ingress zum Backend Service
- Vom Backend Service zum Pod — der Carrier2 läuft
- Carrier2 checkt: Header
x-gefyra: alice? Ja. - Durch den WireGuard-Tunnel
- Durch Cargo auf Alices Laptop
- In ihren lokalen Django Container
Ihr Debugger pausiert. Sie schaut auf den tatsächlichen Request den Bob gerade geschickt hat. Sein exakter Payload. Seine exakten Header. Sein exakter Session State.
Sie steppt durch den Code. Die Payment Method kommt als "credit_card" rein — ein String. Ihr Code erwartet {"type": "credit_card", "provider": "stripe"} — ein Object. Die Validation schlägt fehl. 422.
Sie fixt den Handler so dass er beide Formate akzeptiert für Backwards Compatibility. Speichert. Djangos Hot Reload greift. Der Server startet in unter einer Sekunde neu.
„Nochmal versuchen."
Bob lädt neu. Füllt das Formular aus. Klickt „Bezahlen." Erfolg.
Gesamtzeit von „Ich kriege einen Fehler" bis „Ist gefixt": vier Minuten. Selbes Environment. Selbe Daten. Selbe Umstände. Der Bug existierte in der gemeinsamen Realität, also haben wir ihn in der gemeinsamen Realität debuggt.
Währenddessen arbeitet auch Carol — eine andere Backend-Entwicklerin — am selben Service. Sie hat ihre eigene Bridge:
gefyra bridge create \
--mount backend-mount \
--local carol-backend \
--ports 8000:8000 \
--match-header-exact x-gefyra:carol
Jetzt routet Carrier2:
x-gefyra: alice→ Alices Laptopx-gefyra: carol→ Carols Laptop- Kein matchender Header → Shadow Workload
Drei Versionen des Backends laufen gleichzeitig gegen denselben Cluster State. Die automatisierten Tests des QA-Teams (ohne speziellen Header) treffen den Shadow Workload. Alle arbeiten gleichzeitig, keine Störungen.
Wir nennen das „kollaboratives Debugging statt archäologisches Debugging". Wir graben keine Artefakte aus einer entfernten Umgebung aus und versuchen zu rekonstruieren was passiert ist. Wir stehen beide am selben Ort, schauen auf dieselbe Sache, in Echtzeit.
Traffic Matching Optionen
Die Matching Rules sind flexibel und lassen sich kombinieren:
# Pfad-basiertes Routing
--match-path-exact /api/payments
--match-path-prefix /api/v2/
--match-path-regex ^/admin/.*
# Header-basiertes Routing
--match-header-exact x-gefyra:alice
--match-header-prefix x-team:backend
--match-header-regex x-user:.*test.*
# Kombinationen (AND innerhalb einer Rule, OR über Rules hinweg)
--match-header-exact x-gefyra:alice --match-path-prefix /api/payments
Für Multi-Port-Services handled Carrier2 mehrere Port Mappings:
gefyra bridge create \
--mount my-mount \
--local my-container \
--ports 8080:3000 \
--ports 9000:5000 \
--match-header-exact x-gefyra:alice
Warum WireGuard
Die Wahl des Tunneling-Protokolls klingt nach Implementierungsdetail, aber sie prägt die gesamte Developer Experience.
Wir haben früh verschiedene Optionen evaluiert: SSH-Tunnel, OpenVPN, IPSec, WireGuard. Die Anforderungen waren klar: schneller Verbindungsaufbau, zuverlässiges Reconnect nach Netzwerkwechseln (Laptops schlafen ein, WiFi wechselt, VPNs werden umgeschaltet), und niedriger Overhead für den latenzempfindlichen Traffic den Entwickler produzieren.
┌──────────────┬──────────────┬─────────────────┬──────────────┐
│ │ OpenVPN │ IPSec │ WireGuard │
├──────────────┼──────────────┼─────────────────┼──────────────┤
│ Codebase │ ~100k LOC │ ~400k LOC │ ~4k LOC │
│ Handshake │ Multi-Round │ IKE (komplex) │ 1-RTT │
│ Roaming │ Reconnect │ Komplex │ Nativ │
│ Crypto │ Konfigurierb.│ Konfigurierbar │ Curve25519 │
│ Kernel │ Userspace │ Variiert │ In-Kernel │
└──────────────┴──────────────┴─────────────────┴──────────────┘
WireGuard hat klar gewonnen. Etwa 4.000 Zeilen Code — klein genug zum Auditen, einfach genug um darüber nachzudenken. Es lebt im Linux Kernel (und hat ausgereifte Userspace-Implementierungen für macOS und Windows). Connections bauen sich in einem Single Round-Trip auf weil es keine Negotiation gibt: die kryptografischen Parameter sind fix (Curve25519 für Key Exchange, ChaCha20-Poly1305 für Encryption).
Am wichtigsten für unseren Use Case: WireGuard handled Roaming nativ. Das Protokoll ist stateless was Connection Management angeht — wenn sich deine IP ändert, kommt das nächste Paket einfach von der neuen Adresse, und der Peer akzeptiert es (solange die kryptografische Authentifizierung validiert). Keine Session die neu aufgebaut werden muss, kein Handshake der wiederholt werden muss.
Der praktische Effekt: gefyra connect dauert Sekunden. Unsere Engineers klappen den Laptop für ein Meeting zu, öffnen ihn eine Stunde später, und die Connection ist einfach... da. Sie wechseln von Büro-WiFi zum Handy-Hotspot mitten in einer Debug-Session und nichts geht kaputt. Sie arbeiten von zuhause, aus Cafés, aus Zügen, und der Tunnel überlebt.
Wir haben uns schon an „korrekt aber langsam"-Lösungen verbrannt. Tools die technisch funktionieren aber gerade genug Friction aufbauen dass Entwickler drumherum arbeiten. Geschwindigkeit bei Developer Tools ist kein Luxus — sie macht den Unterschied zwischen einem Tool das Teil des Workflows wird und einem Tool das nach einem Monat aufgegeben wird.
Eine Brücke zählt nur wenn Leute sie täglich überqueren. WireGuard stellt sicher dass sie das tun.
Wie wir mit Shared State umgehen
Die offensichtliche Frage: Wenn alle einen Cluster teilen, was ist mit der Datenbank? Mit kollidierenden Änderungen? Mit dem kaputten Code eines Entwicklers der alle anderen betrifft?
Faire Fragen. Shared State ist der harte Teil. So haben wir gelernt damit umzugehen:
Migrations
Datenbank-Migrations laufen durch unsere CI-Pipeline bevor sie den gemeinsamen Namespace treffen. Der Base State ist immer migration-aktuell — wenn CI eine neue Version in den Dev-Namespace deployed, laufen erst die Migrations. Das Schema ist also immer konsistent mit dem deployed Code.
Wenn jemand an einem Feature arbeitet das Schema-Änderungen braucht, gibt es zwei Optionen:
- Für additive Änderungen (neue Spalten mit Defaults, neue Tabellen): kann man lokal testen indem man die Migration gegen die gemeinsame Datenbank laufen lässt. Gefyras Volume Mounting erlaubt
python manage.py migratelokal während man mit dem Cluster verbunden ist — man modifiziert das echte Schema, mit echten Daten. - Für breaking Änderungen (Spalten entfernen, Typ-Änderungen): nutzt man eine branch-spezifische Datenbank oder einen dedizierten Namespace. Die spinnen wir bei Bedarf für komplexe Features hoch. Der Punkt ist dass schema-brechende Arbeit das gemeinsame Environment nicht anfasst bis sie merge-ready ist.
In der Praxis involviert die meiste tägliche Entwicklung gar keine Migrations, das kommt also selten vor. Aber wenn, dann unterstützt das Tooling es.
Test-Daten-Isolation
Wir nutzen Conventions, kein Tooling. Alle Test-Records die während der Entwicklung erstellt werden haben ein Prefix: alice-test-*, bob-test-*. Low-Tech, aber funktioniert erstaunlich gut.
Die Vorteile sind simpel:
- Jeder kann sehen welche Testdaten existieren
- Niemand modifiziert versehentlich produktionsähnliche Records
- Aufräumen ist einfach:
DELETE FROM orders WHERE id LIKE 'alice-test-%' - Debugging ist einfacher: man weiß welche Records echt sind und welche Test
Wir haben anspruchsvollere Ansätze probiert — automatische Testdaten-Isolation, Shadow-Tabellen, Entwickler-spezifische Schema-Prefixe. Die haben Komplexität hinzugefügt ohne viel Mehrwert zu bringen. Die Convention funktioniert weil jeder sie versteht und befolgt.
Destruktive Operationen
Manche Development-Tasks brauchen destruktive Operationen: Tabellen truncaten, Sequences resetten, Bulk Deletes, Data Backfills. Die passieren nicht im gemeinsamen Namespace.
Wenn jemand TRUNCATE TABLE users braucht um sein Import-Script zu testen, macht er das in seinem eigenen Sandbox-Namespace. Wir haben ein paar davon rumliegen, und Entwickler können sich einen claimen wenn sie ihn brauchen. Der gemeinsame Namespace ist für normale Entwicklung — Code Changes, Debugging, Feature-Arbeit. Er ist kein Spielplatz für Experimente die andere Leute betreffen könnten.
Was ist mit Bridge-Konflikten?
Mit Personal Bridges ist das weitgehend gelöst. Mehrere Entwickler können denselben Service mounten — jeder mit seinem eigenen --match-header-exact x-gefyra:. Carrier2 routet korrekt nach den Matching Rules. Traffic ohne matchenden Header geht zum Shadow Workload. Keine Koordination nötig außer eindeutige Identifier zu wählen.
Gefyra zeigt auch welche Mounts und Bridges aktiv sind:
gefyra mount list
gefyra bridge list
Und wenn was schiefgeht, sind die Symptome sofort offensichtlich — Requests gehen an den falschen Ort und failen. Es korrigiert sich selbst weil man es merkt und fixt.
Team Setup
Für einzelne Entwickler die experimentieren handled gefyra up alles — installiert den Operator, erstellt einen Client, und connected in einem Kommando. Für Teams will man mehr Kontrolle:
Operator Installation (einmalig, durch Admin):
gefyra install
Client-Erstellung (durch Admin, pro Entwickler):
gefyra client create --name alice
gefyra client config alice > alice-client.yaml
gefyra client create --name bob
gefyra client config bob > bob-client.yaml
# Diese Dateien sicher an Entwickler verteilen
Täglicher Developer Workflow:
# Mit bereitgestellter Client-Datei connecten
gefyra connection create -f alice-client.yaml
# Lokalen Container mit Cluster-Connectivity laufen lassen (80% der Arbeit hört hier auf)
gefyra run -i myimage -N mycontainer -n mynamespace \
--env-from deployment/app/main \
-v $(pwd)/src:/app/src
# Wenn nötig: Mount und Bridge mit persönlichem Routing erstellen
gefyra mount create --target deployment/app/main -n mynamespace --name my-mount
gefyra bridge create --mount my-mount --local mycontainer --ports 8000:8000 \
--match-header-exact x-gefyra:alice
Lifecycle Management:
# Sehen was aktiv ist
gefyra list
gefyra mount list
gefyra bridge list
# Aufräumen
gefyra bridge delete my-bridge
gefyra mount delete my-mount # State: ACTIVE → RESTORING → TERMINATED
gefyra connection disconnect
Was wir gelernt haben
Nach zwei Jahren mit diesem Workflow über Dutzende Kundenprojekte haben sich Muster herausgebildet.
Einfach anfangen
Als wir Gefyra ursprünglich gebaut haben, stellten wir uns aufwendige Use Cases vor: Request-basiertes Routing nach Headern, mehrere simultane Bridges, automatisiertes Environment-Provisioning. Wir haben diese Features gebaut. Aber hier ist die Sache: Etwa 80% unserer Entwicklung braucht nur gefyra run — connecten und einen lokalen Container mit Cluster-Connectivity laufen lassen. Kein Bridge nötig.
Die Bridge mit persönlichen Routing Rules handled die restlichen 20% — wenn wir Traffic von Frontends, Webhooks oder anderen Services abfangen müssen. Mit run anfangen. Mounts und Bridges erst hinzufügen wenn man tatsächlich Traffic abfangen muss der woanders herkommt.
Der Cluster ist Infrastruktur, keine Magie
Einen gemeinsamen Development-Cluster zu betreiben braucht dieselbe Sorgfalt wie jede andere Infrastruktur. Jemand muss ihn monitoren, updaten, die Kosten managen, Incidents handlen. Wir haben Alerting wenn der Dev-Cluster unhealthy ist, und wir behandeln Dev-Cluster-Ausfälle mit angemessener (wenn auch nicht Production-Level) Dringlichkeit.
Wenn der Dev-Cluster down ist, können die Entwickler nicht arbeiten. Das ist ein echter Kostenpunkt. Entsprechend budgetieren.
Nicht alles braucht eine Bridge
Der häufigste Fehler den wir sehen (und früh selbst gemacht haben): zu Gefyra greifen wenn ein einfacheres Tool reichen würde.
Wenn du CSS änderst, nimm das Hot Module Replacement deines Frontend Dev Servers. Ist schneller als jede Bridge.
Wenn du eine pure Function ohne externe Dependencies schreibst, schreib einen Unit Test. Lokal laufen lassen. Dafür brauchst du keinen Cluster.
Wenn du einen Typo in einem Template fixst, fix ihn einfach und push. Die CI-Pipeline fängt echte Probleme ab.
Die Bridge ist für Fälle wo die Umstände zählen — wenn der Bug in der Interaktion zwischen deinem Code und seiner Umgebung lebt. Dafür nutzen. Für alles andere einfachere Tools nehmen.
Die Feedback Loop ist alles
Die wichtigste Metrik für Developer Tooling ist: Wie lang dauert es zwischen einer Änderung und dem Wissen ob sie funktioniert?
Bei rein lokaler Entwicklung ist das instant: Datei speichern, Ergebnis sehen.
Bei CI-getriebener Entwicklung sind das Minuten bis Dutzende Minuten: Pushen, warten, checken.
Bei Gefyra-basierter Entwicklung sind das Sekunden: Datei speichern, Hot Reload greift, gebridgter Request nutzt neuen Code.
Wir optimieren diese Loop gnadenlos. Jede Sekunde Latenz ist eine Sekunde gebrochener Fokus. Jede Minute Warten ist eine Minute verlorener Kontext. Die Bridge zählt weil sie die Feedback Loop eng hält während sie erweitert was man testen kann.
Wann wir das nicht nutzen
Wir glauben an Ehrlichkeit bei Tools, besonders bei denen die wir selbst bauen.
| Szenario | Gefyra nutzen? | Warum |
|---|---|---|
| CSS-Tweaks | Nein | Lokaler Dev Server + HMR ist schneller |
| Pure Functions | Nein | Lokal unit-testen |
| Template-Typos | Nein | Einfach pushen, CI fängt's ab |
| Neuer Endpoint (direkt aufrufbar) | Nur run | Kein Traffic zum Abfangen |
| Celery Task Debugging | Nur run | Tasks direkt einreihen |
| Externe API-Integration | Nur run | Brauche Cluster-NAT, nicht Interception |
| Frontend-Backend-Integration | Volle Bridge | Muss Frontend-Requests abfangen |
| Webhook Debugging | Volle Bridge | Muss externe Callbacks abfangen |
| Service Mesh Debugging | Volle Bridge | Kann Istio lokal nicht simulieren |
| Daten-abhängige Bugs | Volle Bridge | Muss das ganze System sehen |
| Mehrere Entwickler am selben Service | Personal Bridges | Jeder Dev kriegt eigenes Routing |
Die Heuristik die wir nutzen: Wenn du es direkt aufrufen kannst, nimm einfach run. Wenn Traffic woanders herkommt, erstelle eine Bridge mit deinen persönlichen Routing Rules.
Von Isolation zu Partizipation
Ortegas Erkenntnis war dass Identität nicht in Isolation existiert. „Ich bin ich und meine Umstände" — das Selbst entsteht aus dem Engagement mit der Welt, nicht aus der Trennung von ihr. Du bist keine abstrakte Essenz die zufällig von Kontext umgeben ist. Du wirst durch diesen Kontext konstituiert, von ihm geformt, untrennbar mit ihm verbunden.
Wir haben festgestellt dass das gleiche für Software gilt. Code existiert nicht im Abstrakten. Er existiert im Gespräch mit Datenbanken, Netzwerken, Services, Konfigurationen, Constraints. Eine Function die in Isolation perfekt funktioniert könnte katastrophal versagen wenn sie mit anderen Functions komponiert wird. Ein Algorithmus der in der Theorie elegant ist könnte in der Praxis desaströs sein. Die Umstände sind nicht nebensächlich für den Code — sie sind essentiell für ihn.
Die Geschichte der Development Environments ist eine Geschichte des Versuchs dieser Wahrheit zu entkommen — Umstände zu simulieren statt sich mit ihnen auseinanderzusetzen. Wir mocken Datenbanken damit wir keine echten brauchen. Wir stubben APIs damit wir keinen Netzwerkzugang brauchen. Wir approximieren Netzwerke mit localhost damit wir keine Infrastruktur brauchen. Jede Simulation tauscht Genauigkeit gegen Geschwindigkeit, Wahrheit gegen Bequemlichkeit.
Lange Zeit hat dieser Trade-off Sinn gemacht. Echte Environments waren langsam zu provisionieren, teuer zu betreiben, und komplex zu managen. Simulation war die pragmatische Wahl.
Kubernetes hat die Gleichung verändert. Nicht indem es Dinge einfacher gemacht hat — das hat es emphatisch nicht — aber indem es Infrastruktur programmierbar gemacht hat. Wenn dein Production Environment in Code definiert ist, von Operatoren gemanaged wird, und aus Version Control reproduzierbar ist, dann kann dein Development Environment an diesem Code partizipieren statt ihn von außen zu simulieren.
Gefyra — γέφυρα, die Brücke — kollabiert die Distanz zwischen Simulation und Partizipation. Das Netzwerk ist echt. Das DNS ist echt. Die Services sind echt. Die Umstände sind intakt. Nur der Code ist lokal, änderbar mit Gedankengeschwindigkeit.
Mit Personal Bridges und Shadow Workloads partizipieren mehrere Entwickler gleichzeitig. Jeder fängt seinen eigenen Traffic ab. Der Shadow handled den Rest. Niemand kommt der Arbeit anderer in die Quere.
Das beste Development Environment ist nicht das das Production am genauesten simuliert. Es ist das das Production ist — mit persönlichen Routing Rules und einem schnellen Undo-Button.
Nach zwei Jahren in denen wir diese Brücke täglich überqueren über Dutzende Kundenprojekte hinweg, können wir uns nicht vorstellen zurückzugehen. Die Fiktionen gegen die wir früher entwickelt haben — die gemockten Services, die synthetischen Daten, die approximierten Netzwerke — fühlen sich an wie Schwimmen lernen auf dem Trockenen.
Handeln, nicht isolieren.
Kurzreferenz
# === Einzelner Entwickler (Quick Start) ===
gefyra up # Operator installieren + connecten
# === Team Workflow ===
# Admin: Operator einmalig installieren
gefyra install
# Admin: Client-Configs erstellen
gefyra client create --name alice
gefyra client config alice > alice-client.yaml
# Entwickler: Connecten
gefyra connection create -f alice-client.yaml
# === Lokalen Container laufen lassen (80% der Arbeit) ===
gefyra run \
-i <image> \
-N <container-name> \
-n <namespace> \
--env-from deployment/<n>/<container> \
-v $(pwd)/src:/app/src \
-p <port>:<port>
# === Traffic bridgen (wenn nötig) ===
# Schritt 1: Mount erstellen (bereitet Infrastruktur vor)
gefyra mount create \
--target deployment/<n>/<container> \
--namespace <namespace> \
--name <mount-name>
# Schritt 2: Bridge mit persönlichem Routing erstellen
gefyra bridge create \
--mount <mount-name> \
--local <container-name> \
--ports <cluster-port>:<local-port> \
--match-header-exact x-gefyra:<deine-id>
# === Traffic Matching Optionen ===
--match-path-exact /api/users
--match-path-prefix /api/
--match-path-regex ^/admin/.*
--match-header-exact x-gefyra:alice
--match-header-prefix x-team:backend
--match-header-regex x-user:.*test.*
# === Inspektion ===
gefyra list
gefyra mount list
gefyra bridge list
# === Aufräumen ===
gefyra bridge delete <bridge-name>
gefyra mount delete <mount-name>
gefyra connection disconnect
Gefyra ist Open Source. Gebaut und maintained von Blueshoe. Wenn du diesen Workflow für dein Team willst aber nicht weißt wo du anfangen sollst, können wir helfen.
Hast du noch Fragen oder eine Meinung? Mit deinem GitHub Account kannst Du es uns wissen lassen...
Was unsere Kunden über uns sagen
- Ofa Bamberg GmbHB2B Online-Shop | B2C Website | Hosting | Betreuung | Security
- Ludwig-Maximilians-Universität MünchenPlattformentwicklung | Hosting | Betreuung | APIs | Website
Blueshoe hat unsere Forschungsdatenplattform Munich Media Monitoring (M3) entwickelt und uns hervorragend dabei beraten. Das Team hat unsere Anforderungen genau verstanden und sich aktiv in die Ausgestaltung der Software und der Betriebsumgebung eingebracht. Wir sind froh, dass auch Wartung und weiterführender Support in Blueshoes Händen liegen.
- Deutsches MuseumDigitalisierung | Beratung | Datenbank-Optimierung | GraphQL | CMSFoto: Anne Göttlicher
Im Rahmen eines komplexen Digitalisierungsprojekts für unsere Exponate-Datenbank war Blueshoe ein äußerst verlässlicher Partner. Sie haben uns nicht nur während des gesamten Projekts hervorragend beraten, sondern unsere Anforderungen perfekt umgesetzt. Dank ihrer Arbeit ist unsere Datenbank nun ein bedeutender Mehrwert für die weltweite wissenschaftliche Forschung.
- Fonds Finanz Maklerservice GmbHPlattformentwicklung | Prozess-Systeme | Hosting | Betreuung | Zertifikate | Website© Fonds Finanz Maklerservice GmbH
Blueshoe ist unsere verlängerte Werkbank für Entwicklung, Wartung und Support unserer Weiterbildungs- und Zertifizierungsplattformen. Das Team hat sich gründlich in unsere Abläufe eingearbeitet, und wir freuen uns, Blueshoe als zuverlässigen Partner an unserer Seite zu haben.
- Technische Universität HamburgPlattformentwicklung | Beratung | Prozess-Systeme | Hosting | Website
Seit 2019 unterstützt uns die Blueshoe GmbH tatkräftig bei der Entwicklung und Weiterentwicklung des "Digital Learning Lab" und der "Digital Learning Tools". Dank ihrer Beratung konnten wir von Anfang an auf eine zukunftssichere, moderne technische Struktur setzen. Die Zusammenarbeit ist reibungslos, und wir fühlen uns rundum gut betreut. Und davon profitieren dann auch die Lehrkräfte in Hamburg.





















