Cuando sabes que tu aplicación va a ir a producción en Kubernetes desde el minuto cero, lo último que quieres es mantener un docker-compose.yml, un .aspire.app, un conjunto de manifiestos k8s/, y encima repetir la configuración en CI/CD. La tentación de crear “entornos de desarrollo paralelos” es alta, pero el coste a medio plazo también lo es.
En este artículo, te muestro cómo estructurar un entorno de desarrollo eficiente, realista y sin lock-in, comparando 4 enfoques comunes: Docker Compose, .NET Aspire, k3d y kind, desde la perspectiva de un equipo que desplegará sí o sí en Kubernetes.
Objetivo
- 
Acelerar el desarrollo local. 
- 
Sin reinventar la infraestructura. 
- 
Sin definir entornos “alternativos” que no se parecen a producción. 
- 
Sin depender de soluciones propietarias o ligadas a un proveedor cloud. 
El problema de duplicar entornos
“Lo levanto con Compose en local y ya en CI que use K8s.”
Error común. Lo que funciona en Compose suele tener diferencias sutiles: redes, volúmenes, healthchecks, secretos, puertos, etc.
“Uso Aspire para desarrollo, luego me paso a Helm y ya está.”
Otra trampa. Aspire abstrae muchas cosas útiles, pero introduce una capa adicional que no es la misma que usar manifiestos Kubernetes nativos.
Conclusión: Si sabes que vas a Kubernetes, no diseñes tu entorno local como si no fueras a ir.
Estrategia recomendada: Kubernetes como fuente de verdad
Define una sola vez tus servicios como manifiestos YAML para K8s:
- 
deployment.yaml,service.yaml,configmap.yaml, etc.
- 
Usa Helm, Kustomize o raw YAML, pero que sea compatible desde el primer día con producción. 
¿Y qué pasa con Compose o Aspire?
Docker Compose
- 
Es cómodo. 
- 
Pero no es Kubernetes. 
- 
Te obliga a mantener definiciones redundantes. 
.NET Aspire
- 
Genial si eres 100% .NET y quieres máxima productividad. 
- 
Pero no es Kubernetes-compatible. 
- 
Puedes usarlo como “shell de desarrollo”, pero vas a tener que escribir de nuevo los manifiestos de producción. 
Flujo ideal de desarrollo (desde el día 0)
 
			- 
Desarrollas con el mismo manifiesto que desplegarás. 
- 
Puedes testear servicios, pods, configuración real, observabilidad… 
- 
Puedes hacer port forwarding ( kubectl port-forward), logs, secretos…
Acelerar sin duplicar: tips prácticos
| Necesidad | Solución | 
|---|---|
| Base de datos local persistente | PersistentVolumeClaimconlocal-path-provisioner | 
| Acceso desde navegador a servicios | kubectl port-forwardoIngressconk3d | 
| Rebuild rápido del microservicio | skaffold devotilt | 
| Debug paso a paso | Visual Studio + kubectl port-forwardal puerto del API | 
| SQL de desarrollo pre-cargado | initContainersen elDeploymentdel PostgreSQL | 
¿Y las herramientas de desarrollo?
Puedes usar herramientas como Tilt o Skaffold para acelerar los ciclos de desarrollo sin dejar de usar Kubernetes.
- 
Tilt: recarga automática de código en el contenedor, integración con Helm/Kustomize. 
- 
Skaffold: build, deploy, y test en ciclo continuo. 
Ambas trabajan sobre k3d/kind o cualquier clúster K8s.
¿Y si quiero simplificar el onboarding?
Crea un script o
Makefilecon estos pasos:
# Arrancar entorno local
make dev
# Internamente:
# - arranca k3d (si no existe)
# - aplica manifiestos
# - muestra servicios disponiblesTambién puedes publicar una imagen devcontainer con todo preconfigurado (Docker + kubectl + k3d + tilt).
Conclusión
Si ya sabes que tu app va a Kubernetes, elimina la idea de que necesitas un entorno local alternativo. Usa Kubernetes desde el principio y hazlo ligero, reproducible y portable con herramientas como k3d, kind, Tilt o Skaffold.
El mejor entorno de desarrollo no es el más cómodo: es el más parecido a producción.
Ejemplo completo con kind + Tilt + .NET
Cuando sabes que tu aplicación va a Kubernetes, no tiene sentido mantener docker-compose.yml, scripts alternativos ni setups exclusivos para desarrollo.
La clave está en usar Kubernetes también en local, pero sin complicar la vida a los desarrolladores.
Este artículo muestra un ejemplo real y completo para levantar localmente:
- 
Un backend en .NET 8 Web API 
- 
Un frontend estático en Nginx 
- 
Una base de datos PostgreSQL 
- 
Todo orquestado en un clúster kind
- 
Ciclo de vida gestionado por Tilt
- 
Automatizado vía Makefile
🎯 Objetivo: cero duplicación entre desarrollo y producción.
¿Qué vamos a montar?
[frontend] ──────▶ HTTP :5000
    │
    ▼
[backend] ───────▶ HTTP :8000
    │
    ▼
[postgres] ──────▶ DB :5432Cada componente será desplegado como un pod de Kubernetes, con sus Deployment y Service, directamente sobre un clúster local de kind.
Estructura del proyecto
microservices-kind-tilt-example/
├── Makefile
├── tiltfile
├── k8s/
│   ├── backend/
│   ├── frontend/
│   └── postgres/
├── src/
│   ├── backend/
│   └── frontend/Paso 1: Automatización con Makefile
KIND_CLUSTER_NAME=dev-cluster
.PHONY: kind-start kind-delete dev
kind-start:
\tkind create cluster --name $(KIND_CLUSTER_NAME) --wait 60s
kind-delete:
\tkind delete cluster --name $(KIND_CLUSTER_NAME)
dev: kind-start
\ttilt upPaso 2: Definir los manifiestos Kubernetes
PostgreSQL
# k8s/postgres/postgres-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16
        env:
        - name: POSTGRES_DB
          value: "products"
        - name: POSTGRES_USER
          value: "user"
        - name: POSTGRES_PASSWORD
          value: "pass"
        ports:
        - containerPort: 5432Backend (.NET 8)
# k8s/backend/backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: backend
        image: backend
        env:
        - name: ConnectionStrings__Default
          value: "Host=postgres;Database=products;Username=user;Password=pass"
        ports:
        - containerPort: 80# k8s/backend/backend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: backend
spec:
  selector:
    app: backend
  ports:
  - port: 80
    targetPort: 80Frontend (estático)
# k8s/frontend/frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: frontend
        ports:
        - containerPort: 80# k8s/frontend/frontend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: frontend
spec:
  selector:
    app: frontend
  ports:
  - port: 80
    targetPort: 80Paso 3: Dockerfiles
Backend (.NET 8 Web API)
# src/backend/Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app/out
FROM base AS final
WORKDIR /app
COPY --from=build /app/out .
ENTRYPOINT ["dotnet", "ProductService.Api.dll"]Frontend (Nginx con HTML)
# src/frontend/Dockerfile
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.htmlPaso 4: Tiltfile para levantar todo
# tiltfile
k8s_yaml([
  "k8s/postgres/postgres-deployment.yaml",
  "k8s/postgres/postgres-service.yaml",
  "k8s/backend/backend-deployment.yaml",
  "k8s/backend/backend-service.yaml",
  "k8s/frontend/frontend-deployment.yaml",
  "k8s/frontend/frontend-service.yaml"
])
docker_build('backend', './src/backend')
docker_build('frontend', './src/frontend')
k8s_resource('backend', port_forwards=8000)
k8s_resource('frontend', port_forwards=5000)Paso 5: Ejecutar
make dev- 
Abre automáticamente la interfaz de Tilt ( http://localhost:10350)
- 
Accede al frontend en: http://localhost:5000 
- 
Accede al backend en: http://localhost:8000 
Resultado
Con este enfoque:
- 
Usas Kubernetes desde el principio 
- 
No hay necesidad de docker-compose
- 
Todo está preparado para pasar a producción sin reescribir nada 
- 
Tilt te ofrece recarga automática, logs y control visual 
Una única fuente de verdad: tus manifiestos Kubernetes.
Depuración local + dependencias remotas (en kind)
Beneficios
- 
VS Code usa su propio debugger. 
- 
No necesitas instalar nada especial en el contenedor. 
- 
Puedes hacer hot reload, breakpoints y todo lo habitual. 
- 
Solo el backend se ejecuta localmente, todo lo demás sigue en Kubernetes. 
Paso 1: Ejecuta kind y Tilt normalmente
make devEsto desplegará PostgreSQL, el frontend y el backend dentro de Kubernetes.
Paso 2: Elimina el pod de backend para depurar localmente
kubectl delete deployment backendAsí liberas el puerto 80 que usaba el pod backend dentro del clúster, para que tu versión local pueda conectarse.
Paso 3: Haz port-forward de PostgreSQL al host
kubectl port-forward svc/postgres 5432:5432Esto expone la base de datos del clúster en localhost:5432, permitiendo que tu backend local se conecte sin problemas.
Paso 4: Configura launch.json en VS Code
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": ".NET Core Launch (backend local)",
      "type": "coreclr",
      "request": "launch",
      "preLaunchTask": "build",
      "program": "${workspaceFolder}/bin/Debug/net8.0/ProductService.Api.dll",
      "args": [],
      "cwd": "${workspaceFolder}",
      "stopAtEntry": false,
      "serverReadyAction": {
        "action": "openExternally",
        "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
      },
      "env": {
        "ConnectionStrings__Default": "Host=localhost;Database=products;Username=user;Password=pass"
      },
      "launchBrowser": {
        "enabled": true,
        "args": "http://localhost:5000"
      }
    }
  ]
}
Asegúrate de que launchSettings.json esté configurado para escuchar en http://localhost:5000.
Paso 5: Levanta el backend desde VS Code
En la raíz del proyecto src/backend, pulsa F5 o selecciona el perfil «backend local» desde la vista de ejecución.
Ya puedes depurar línea por línea, ver el tráfico, modificar código, etc.
(Opcional) Comunicar el frontend con el backend local
Si quieres que el frontend (que sigue en Kubernetes) hable con tu backend en localhost, necesitarás exponer tu backend en la red de Docker o de kind. Dos formas comunes:
Opción rápida: Ingresar tu IP local
En tu HTML o config del frontend:
const API_URL = "http://host.docker.internal:5000"; // Si Tilt está dentro de DockerO expón el backend desde host a kind con:
kubectl port-forward svc/frontend 5000:80Y redirige las llamadas desde el frontend al backend vía host expuesto.
Alternativa: usar Tilt para desarrollo mixto
Puedes indicar a Tilt que no construya ni despliegue el backend y que lo asuma como externo:
# tiltfile (modificado)
disable_yaml('k8s/backend/backend-deployment.yaml', 'k8s/backend/backend-service.yaml')Así Tilt sigue levantando todo lo demás y tú controlas el backend desde tu IDE. Y lógicamente es la que deberías usar.
Resultado
- 
PostgreSQL y frontend siguen en Kubernetes. 
- 
Backend se ejecuta desde VS Code con toda la potencia del debugger. 
- 
No duplicas infraestructura. 
- 
El comportamiento es idéntico al entorno productivo. 
¿Cómo se integra en el ejemplo con kind + Tilt?
Vamos a dar un paso más, ya que si quieres que tu entorno de desarrollo se parezca de verdad a producción, usas un Ingress Controller, tambien puedes ponerlo en local.
En este caso vamos a usar Traefik.
¿Qué es Traefik?
Traefik es un ingress controller dinámico que:
- 
Se despliega dentro de Kubernetes. 
- 
Detecta automáticamente tus servicios y rutas. 
- 
Expone tus aplicaciones al exterior mediante HTTP(S), con soporte para: - 
Enrutamiento por hostname o path. 
- 
TLS automático con Let’s Encrypt. 
- 
Middleware: autenticación, headers, rate limit, etc. 
 
- 
¿Por qué meter Traefik en desarrollo?
Porque ayuda a:
| Ventaja | ¿Por qué es útil en local? | 
|---|---|
| Simula producción | Puedes replicar cómo funciona tu ingress en cloud (AKS, EKS…) | 
| Acceso uniforme | Accedes a servicios con URLs como http://frontend.localhost,http://api.localhost | 
| TLS opcional | Puedes simular HTTPS en desarrollo con certificados locales | 
| Middleware real | Pruebas CORS, auth, redirecciones, etc., sin hacks | 
| Centraliza entrada | Un solo punto de entrada para todos tus servicios | 
¿Cómo se integra en el ejemplo con kind + Tilt?
Paso 1: Instalar Traefik en el clúster kind
Vamos a usar Helm.
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik \
  --set service.type=NodePort \
  --set ports.web.nodePort=30080 \
  --set ports.websecure.nodePort=30443Esto expone:
- 
HTTP en localhost:30080
- 
HTTPS en localhost:30443
Paso 2: Añadir IngressRoutes para frontend y backend
# k8s/ingress/frontend-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: frontend-ingress
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
  rules:
    - host: frontend.localhost
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend
                port:
                  number: 80# k8s/ingress/backend-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: backend-ingress
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
  rules:
    - host: api.localhost
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: backend
                port:
                  number: 80Paso 3: Modificar /etc/hosts para acceder con dominios locales
127.0.0.1 frontend.localhost api.localhostPaso 4: Tilt + Traefik
k8s_yaml([
  # ... anteriores ...
  "k8s/ingress/frontend-ingress.yaml",
  "k8s/ingress/backend-ingress.yaml"
])
Ahora puedes acceder a:
- 
Frontend: http://frontend.localhost:30080 
- 
Backend: http://api.localhost:30080 
Casos de uso que Traefik te permite probar en local
| Escenario | ¿Qué puedes simular con Traefik? | 
|---|---|
| Enrutamiento por subdominio | api.localhost,auth.localhost | 
| Rutas específicas | /api,/admin,/healthz | 
| TLS y certificados | HTTPS con certificados reales o de desarrollo | 
| Redirecciones automáticas | HTTP → HTTPS o paths | 
| Middleware de seguridad | Autenticación básica, CORS, cabeceras seguras | 
Si quieres que tu entorno de desarrollo se parezca de verdad a producción, usa Traefik desde el principio. Especialmente útil si:
- 
Vas a desplegar con Ingress Controllers reales (Traefik, NGINX, Istio). 
- 
Necesitas probar CORS, rutas o dominios personalizados. 
- 
Quieres acceder fácilmente sin recordar puertos por servicio. 
Proyecto 110 % funcional para experimentar sin trampas
Todo lo que has leído en este artículo está empaquetado en un ejemplo completo (la version final con Traefik), funcional y replicable que puedes clonar, ejecutar y extender. Aquí no hay simulaciones ni atajos: lo que montas en local es lo mismo que irá a producción, solo que más ligero.
 
						 
							





