diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..17f7223828 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,51 @@ +# Git +.git +.gitignore + +# Build artifacts +apps/backend/bin/ +apps/web/.next/ +apps/web/out/ +dist/ + +# Dependencies (rebuilt in Docker) +node_modules/ +apps/web/node_modules/ +apps/cli/node_modules/ +apps/packages/*/node_modules/ + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Docker +Dockerfile +.dockerignore + +# CI/CD +.github/ + +# Documentation and config not needed at build time +docs/ +k8s/ +*.md +!apps/web/src/**/*.md +LICENSE + +# Test artifacts +coverage/ +*.test.ts +*.test.go +**/*_test.go + +# Agent/Claude config +.claude/ +.agents/ +CLAUDE.md +AGENTS.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 60f7e847d4..bc9e96fdb7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,7 @@ on: permissions: contents: write + packages: write jobs: build-web: @@ -114,6 +115,41 @@ jobs: dist/kandev-${{ matrix.platform }}.tar.gz dist/kandev-${{ matrix.platform }}.tar.gz.sha256 + docker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/kdlbs/kandev + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha + + - uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + publish-release: needs: build-bundles runs-on: ubuntu-latest diff --git a/AGENTS.md b/AGENTS.md index 5594ab8b39..b66682a492 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,6 +18,8 @@ apps/ - **Backend**: Go with Make (`make -C apps/backend test|lint|build`) - **Frontend**: Next.js (`cd apps && pnpm --filter @kandev/web dev|lint|typecheck`) - **UI**: Shadcn components via `@kandev/ui` +- **GitHub repo**: `https://github.com/kdlbs/kandev` +- **Container image**: `ghcr.io/kdlbs/kandev` (GitHub Container Registry) --- diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..e4a1917bef --- /dev/null +++ b/Dockerfile @@ -0,0 +1,111 @@ +# Kandev Server Dockerfile +# Multi-stage build: Go backend + Next.js frontend + CLI launcher +# +# Build: +# docker build -t kandev:latest . +# +# Run: +# docker run -p 8080:8080 -p 3000:3000 -v kandev-data:/data kandev:latest + +# --------------------------------------------------------------------------- +# Stage 1: Go builder — compile kandev + agentctl binaries +# --------------------------------------------------------------------------- +FROM golang:1.26-bookworm AS go-builder + +WORKDIR /build + +# Cache Go module downloads +COPY apps/backend/go.mod apps/backend/go.sum ./ +RUN go mod download + +# Copy backend source and build +COPY apps/backend/ ./ + +RUN go build -ldflags "-s -w" -o /out/kandev ./cmd/kandev && \ + go build -ldflags "-s -w" -o /out/agentctl ./cmd/agentctl + +# --------------------------------------------------------------------------- +# Stage 2: Web + CLI builder — build Next.js standalone + CLI +# --------------------------------------------------------------------------- +FROM node:24-slim AS web-builder + +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /build/apps + +# Copy workspace config and all package.json files for dependency caching +COPY apps/package.json apps/pnpm-workspace.yaml apps/pnpm-lock.yaml ./ +COPY apps/web/package.json ./web/package.json +COPY apps/cli/package.json ./cli/package.json +COPY apps/packages/ui/package.json ./packages/ui/package.json +COPY apps/packages/theme/package.json ./packages/theme/package.json +COPY apps/packages/types/package.json ./packages/types/package.json + +RUN pnpm install --frozen-lockfile + +# Copy full source for build +COPY apps/ ./ + +# Build shared packages, web app, and CLI +RUN pnpm --filter @kandev/web build && \ + pnpm --filter kandev build + +# --------------------------------------------------------------------------- +# Stage 3: Runtime — minimal image with both services +# --------------------------------------------------------------------------- +FROM node:24-bookworm-slim AS runtime + +# Install only essential runtime dependencies, then clean up +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + git \ + ca-certificates \ + tini && \ + rm -rf /var/lib/apt/lists/* + +# Create app directory structure matching what the CLI expects: +# /app/apps/backend/bin/kandev +# /app/apps/web/.next/standalone/web/server.js +RUN mkdir -p /app/apps/backend/bin /app/apps/web/.next /data/worktrees + +# Copy Go binaries +COPY --from=go-builder /out/kandev /app/apps/backend/bin/kandev +COPY --from=go-builder /out/agentctl /usr/local/bin/agentctl + +# Copy Next.js standalone output +COPY --from=web-builder /build/apps/web/.next/standalone/ /app/apps/web/.next/standalone/ + +# Copy static assets and public directory into standalone output +COPY --from=web-builder /build/apps/web/.next/static/ /app/apps/web/.next/standalone/web/.next/static/ +COPY --from=web-builder /build/apps/web/public/ /app/apps/web/.next/standalone/web/public/ + +# Install CLI: copy built output + package.json, install prod deps, link binary +COPY --from=web-builder /build/apps/cli/dist/ /usr/local/lib/kandev-cli/dist/ +COPY --from=web-builder /build/apps/cli/bin/ /usr/local/lib/kandev-cli/bin/ +COPY --from=web-builder /build/apps/cli/package.json /usr/local/lib/kandev-cli/ +RUN cd /usr/local/lib/kandev-cli && npm install --omit=dev && \ + chmod +x bin/cli.js && \ + ln -s /usr/local/lib/kandev-cli/bin/cli.js /usr/local/bin/kandev + +# Data directory for SQLite and worktrees +VOLUME ["/data"] + +# Environment defaults for containerized operation +ENV KANDEV_NO_BROWSER=1 \ + KANDEV_DATABASE_PATH=/data/kandev.db \ + KANDEV_WORKTREE_BASEPATH=/data/worktrees \ + KANDEV_DOCKER_ENABLED=false \ + HOSTNAME=0.0.0.0 \ + NODE_ENV=production + +# Run as non-root +RUN chown -R node:node /app /data +USER node + +WORKDIR /app + +EXPOSE 8080 3000 + +# tini as PID 1 for signal handling; CLI manages backend + web processes +ENTRYPOINT ["tini", "--"] +CMD ["kandev", "start", "--backend-port", "8080", "--web-port", "3000", "--verbose"] diff --git a/apps/cli/src/start.ts b/apps/cli/src/start.ts index b4d6e95ccf..1e920a0df6 100644 --- a/apps/cli/src/start.ts +++ b/apps/cli/src/start.ts @@ -110,7 +110,7 @@ export async function runStart({ // Production mode: use warn log level for clean output unless verbose/debug const showOutput = verbose || debug; - const dbPath = path.join(DATA_DIR, "kandev.db"); + const dbPath = process.env.KANDEV_DATABASE_PATH || path.join(DATA_DIR, "kandev.db"); const logLevel = debug ? "debug" : verbose ? "info" : "warn"; const backendEnv = buildBackendEnv({ ports, diff --git a/docs/docker.md b/docs/docker.md new file mode 100644 index 0000000000..df97133636 --- /dev/null +++ b/docs/docker.md @@ -0,0 +1,217 @@ +# Docker Guide + +Run Kandev in a Docker container. For Kubernetes deployment, see [k8s.md](k8s.md). + +## Quick Start + +```bash +docker run -p 8080:8080 -p 3000:3000 -v kandev-data:/data ghcr.io/kdlbs/kandev:latest +``` + +Open `http://localhost:3000` in your browser. + +## Using the Pre-built Image + +Kandev publishes images to GitHub Container Registry for `linux/amd64` and `linux/arm64`: + +```bash +# Latest release +docker pull ghcr.io/kdlbs/kandev:latest + +# Specific version +docker pull ghcr.io/kdlbs/kandev:0.9.0 +``` + +## Building from Source + +From the repository root: + +```bash +# Build for your current architecture +docker build -t kandev:latest . + +# Build for a specific architecture +docker build --platform linux/amd64 -t kandev:latest . +``` + +## Data Persistence + +Kandev stores its SQLite database and git worktrees in `/data`. Mount a volume to persist data across container restarts: + +```bash +# Named volume (recommended) +docker run -v kandev-data:/data ghcr.io/kdlbs/kandev:latest + +# Bind mount to a host directory +docker run -v /path/on/host:/data ghcr.io/kdlbs/kandev:latest +``` + +Without a volume, data is lost when the container is removed. + +## Configuration + +Configuration is done via `KANDEV_`-prefixed environment variables: + +```bash +docker run -p 8080:8080 -p 3000:3000 \ + -v kandev-data:/data \ + -e KANDEV_LOG_LEVEL=debug \ + ghcr.io/kdlbs/kandev:latest +``` + +### Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `KANDEV_DATABASE_DRIVER` | `sqlite` | Database driver (`sqlite` or `postgres`) | +| `KANDEV_DATABASE_PATH` | `/data/kandev.db` | SQLite database file path | +| `KANDEV_LOG_LEVEL` | `info` | Log level: `debug`, `info`, `warn`, `error` | +| `KANDEV_LOGGING_FORMAT` | `text` | Log format: `text` or `json` | +| `KANDEV_DOCKER_ENABLED` | `false` | Enable Docker runtime for agents (see below) | +| `KANDEV_WORKTREE_BASEPATH` | `/data/worktrees` | Git worktree storage directory | + +### PostgreSQL + +To use PostgreSQL instead of SQLite: + +```bash +docker run -p 8080:8080 -p 3000:3000 \ + -e KANDEV_DATABASE_DRIVER=postgres \ + -e KANDEV_DATABASE_HOST=host.docker.internal \ + -e KANDEV_DATABASE_PORT=5432 \ + -e KANDEV_DATABASE_USER=kandev \ + -e KANDEV_DATABASE_PASSWORD=secret \ + -e KANDEV_DATABASE_DBNAME=kandev \ + ghcr.io/kdlbs/kandev:latest +``` + +## Ports + +| Port | Service | +|------|---------| +| `8080` | Backend API + WebSocket | +| `3000` | Web UI | + +Override ports by changing the published ports and passing CLI flags: + +```bash +docker run -p 9080:9080 -p 4000:4000 \ + -v kandev-data:/data \ + ghcr.io/kdlbs/kandev:latest \ + kandev start --backend-port 9080 --web-port 4000 +``` + +## Docker Compose + +Create a `docker-compose.yml`: + +```yaml +services: + kandev: + image: ghcr.io/kdlbs/kandev:latest + ports: + - "8080:8080" + - "3000:3000" + volumes: + - kandev-data:/data + restart: unless-stopped + +volumes: + kandev-data: +``` + +```bash +docker compose up -d +``` + +### With PostgreSQL + +```yaml +services: + kandev: + image: ghcr.io/kdlbs/kandev:latest + ports: + - "8080:8080" + - "3000:3000" + volumes: + - kandev-data:/data + environment: + KANDEV_DATABASE_DRIVER: postgres + KANDEV_DATABASE_HOST: postgres + KANDEV_DATABASE_PORT: "5432" + KANDEV_DATABASE_USER: kandev + KANDEV_DATABASE_PASSWORD: secret + KANDEV_DATABASE_DBNAME: kandev + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + + postgres: + image: postgres:17 + environment: + POSTGRES_USER: kandev + POSTGRES_PASSWORD: secret + POSTGRES_DB: kandev + volumes: + - postgres-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U kandev"] + interval: 5s + timeout: 3s + retries: 5 + restart: unless-stopped + +volumes: + kandev-data: + postgres-data: +``` + +## Docker-in-Docker (Agent Containers) + +By default, `KANDEV_DOCKER_ENABLED=false` inside the container. To enable Docker-based agent execution, mount the Docker socket: + +```bash +docker run -p 8080:8080 -p 3000:3000 \ + -v kandev-data:/data \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e KANDEV_DOCKER_ENABLED=true \ + ghcr.io/kdlbs/kandev:latest +``` + +> **Note:** Mounting the Docker socket gives the container full access to the host's Docker daemon. Only do this in trusted environments. + +## Health Check + +The backend exposes a `/health` endpoint: + +```bash +curl http://localhost:8080/health +``` + +For Docker health checks in compose: + +```yaml +healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 15s +``` + +## Troubleshooting + +```bash +# View logs +docker logs kandev + +# Follow logs +docker logs -f kandev + +# Shell into the container +docker exec -it kandev /bin/bash + +# Check data volume +docker volume inspect kandev-data +``` diff --git a/docs/k8s.md b/docs/k8s.md new file mode 100644 index 0000000000..db7346382f --- /dev/null +++ b/docs/k8s.md @@ -0,0 +1,202 @@ +# Kubernetes Deployment Guide + +This guide covers building the Kandev Docker image and deploying it to a Kubernetes cluster. + +## Prerequisites + +- Docker (for building the image) +- A container registry (Docker Hub, GHCR, ECR, etc.) +- A Kubernetes cluster with `kubectl` configured +- A StorageClass that supports `ReadWriteOnce` PVCs (for SQLite persistence) + +## Building the Image + +From the repository root: + +```bash +# Build for your current architecture +docker build -t kandev:latest . + +# Build for a specific architecture (e.g., for amd64 clusters) +docker build --platform linux/amd64 -t kandev:latest . + +# Multi-arch build (requires buildx) +docker buildx build --platform linux/amd64,linux/arm64 -t kandev:latest . +``` + +### Using the Pre-built Image + +Kandev publishes images to GitHub Container Registry. Pull directly: + +```bash +docker pull ghcr.io/kdlbs/kandev:latest +``` + +Or reference it in your K8s deployment: + +```yaml +image: ghcr.io/kdlbs/kandev:latest +``` + +## Deploying to Kubernetes + +### Quick Start + +```bash +# Apply all manifests +kubectl apply -f k8s/ + +# Check status +kubectl get pods -l app=kandev +kubectl logs -l app=kandev -f +``` + +### What Gets Created + +| Resource | File | Purpose | +|----------|------|---------| +| Deployment | `deployment.yaml` | Single-replica pod running backend + web | +| Service | `service.yaml` | ClusterIP exposing ports 8080 (API) and 3000 (UI) | +| ConfigMap | `configmap.yaml` | Non-sensitive environment configuration | +| PVC | `pvc.yaml` | 10Gi persistent volume for SQLite + worktrees | +| Ingress | `ingress.yaml` | Example ingress with WebSocket support | + +### Accessing the UI + +**Port-forward** (quickest for testing): + +```bash +kubectl port-forward svc/kandev 3000:3000 8080:8080 +# Open http://localhost:3000 +``` + +**Ingress**: Edit `k8s/ingress.yaml` to set your domain, then apply. The ingress routes `/api` and `/ws` to the backend (port 8080) and everything else to the web UI (port 3000). + +## Configuration + +Kandev reads configuration via `KANDEV_`-prefixed environment variables (Viper). Set these in `k8s/configmap.yaml` or as environment variables in the deployment. + +### Core Settings + +| Env Var | Default | Description | +|---------|---------|-------------| +| `KANDEV_SERVER_PORT` | `8080` | Backend API port | +| `KANDEV_DATABASE_DRIVER` | `sqlite` | Database driver (`sqlite` or `postgres`) | +| `KANDEV_DATABASE_PATH` | `/data/kandev.db` | SQLite database file path | +| `KANDEV_DOCKER_ENABLED` | `false` | Enable Docker runtime for agents (requires DinD) | +| `KANDEV_LOG_LEVEL` | `info` | Log level: `debug`, `info`, `warn`, `error` | +| `KANDEV_LOGGING_FORMAT` | auto | Log format: `json` (auto-detected in K8s) or `text` | +| `KANDEV_WORKTREE_BASEPATH` | `/data/worktrees` | Git worktree storage directory | + +### PostgreSQL Settings (when `KANDEV_DATABASE_DRIVER=postgres`) + +| Env Var | Default | Description | +|---------|---------|-------------| +| `KANDEV_DATABASE_HOST` | `localhost` | PostgreSQL host | +| `KANDEV_DATABASE_PORT` | `5432` | PostgreSQL port | +| `KANDEV_DATABASE_USER` | `kandev` | Database user | +| `KANDEV_DATABASE_PASSWORD` | (empty) | Database password | +| `KANDEV_DATABASE_DBNAME` | `kandev` | Database name | +| `KANDEV_DATABASE_SSLMODE` | `disable` | SSL mode | + +## Database: SQLite vs PostgreSQL + +### SQLite (default) + +- Zero-config, works out of the box +- Database stored at `/data/kandev.db` on the PV +- **Single replica only** (SQLite is single-writer) +- Deployment strategy is `Recreate` to prevent concurrent writes +- Good for small teams / personal use + +### PostgreSQL (recommended for production) + +- Supports multiple replicas for horizontal scaling +- Change deployment strategy to `RollingUpdate` +- Set via environment variables: + +```yaml +# In configmap.yaml or a Secret +KANDEV_DATABASE_DRIVER: postgres +KANDEV_DATABASE_HOST: postgres.default.svc.cluster.local +KANDEV_DATABASE_PORT: "5432" +KANDEV_DATABASE_USER: kandev +KANDEV_DATABASE_PASSWORD: +KANDEV_DATABASE_DBNAME: kandev +``` + +When using Postgres, the PVC is still needed for worktree storage but the database itself is external. + +## Persistent Storage + +The PVC at `/data` stores: + +- **SQLite database** (`/data/kandev.db`, `/data/kandev.db-wal`, `/data/kandev.db-shm`) +- **Git worktrees** (`/data/worktrees/`) for workspace isolation + +The PVC uses `ReadWriteOnce` access mode. If your cluster requires a specific StorageClass, add it to `k8s/pvc.yaml`: + +```yaml +spec: + storageClassName: your-storage-class +``` + +## Health Checks + +The deployment includes both probes on the backend `/health` endpoint: + +- **Liveness probe**: Restarts the pod if the backend becomes unresponsive (30s interval, 3 failures) +- **Readiness probe**: Removes the pod from service during startup or issues (10s interval, 3 failures) + +The CLI launcher also performs an internal health check — it waits for the backend to be healthy before starting the web server. + +## Scaling + +**Single replica (SQLite)**: The default configuration uses `replicas: 1` with `Recreate` strategy. This ensures only one instance writes to SQLite at a time. + +**Multiple replicas (PostgreSQL)**: Switch to Postgres, change the deployment strategy to `RollingUpdate`, and increase replicas: + +```yaml +spec: + replicas: 3 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 +``` + +## Upgrading + +```bash +# Build and push new image +docker build -t your-registry.com/kandev:v1.1.0 . +docker push your-registry.com/kandev:v1.1.0 + +# Update deployment +kubectl set image deployment/kandev kandev=your-registry.com/kandev:v1.1.0 + +# Or edit the deployment directly +kubectl edit deployment kandev +``` + +SQLite migrations run automatically on startup — no manual migration step needed. + +## Troubleshooting + +```bash +# Check pod status +kubectl get pods -l app=kandev + +# View logs +kubectl logs -l app=kandev -f + +# Shell into the pod (if needed) +kubectl exec -it deployment/kandev -- /bin/bash + +# Check PVC status +kubectl get pvc kandev-data + +# Describe pod for events +kubectl describe pod -l app=kandev +``` diff --git a/k8s/configmap.yaml b/k8s/configmap.yaml new file mode 100644 index 0000000000..6675330193 --- /dev/null +++ b/k8s/configmap.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kandev-config + labels: + app: kandev +data: + # Database — SQLite on the PV. Set KANDEV_DATABASE_DRIVER=postgres for production. + KANDEV_DATABASE_PATH: /data/kandev.db + + # Disable Docker runtime (no Docker-in-Docker in K8s) + KANDEV_DOCKER_ENABLED: "false" + + # Worktree storage on the PV + KANDEV_WORKTREE_BASEPATH: /data/worktrees + + # Logging — auto-detects K8s and uses JSON format + KANDEV_LOG_LEVEL: info diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml new file mode 100644 index 0000000000..9d5e9955c8 --- /dev/null +++ b/k8s/deployment.yaml @@ -0,0 +1,62 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kandev + labels: + app: kandev +spec: + # SQLite is single-writer — use 1 replica. Switch to Postgres for horizontal scaling. + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: kandev + template: + metadata: + labels: + app: kandev + spec: + containers: + - name: kandev + image: kandev:latest + ports: + - name: backend + containerPort: 8080 + protocol: TCP + - name: web + containerPort: 3000 + protocol: TCP + envFrom: + - configMapRef: + name: kandev-config + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: "2" + memory: 2Gi + livenessProbe: + httpGet: + path: /health + port: backend + initialDelaySeconds: 15 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: backend + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 + volumeMounts: + - name: data + mountPath: /data + volumes: + - name: data + persistentVolumeClaim: + claimName: kandev-data diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml new file mode 100644 index 0000000000..02efb94e26 --- /dev/null +++ b/k8s/ingress.yaml @@ -0,0 +1,50 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: kandev + labels: + app: kandev + annotations: + # Adjust annotations for your ingress controller (nginx, traefik, etc.) + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + # WebSocket support + nginx.ingress.kubernetes.io/proxy-http-version: "1.1" + nginx.ingress.kubernetes.io/configuration-snippet: | + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; +spec: + rules: + - host: kandev.example.com + http: + paths: + # Backend API + WebSocket + - path: /api + pathType: Prefix + backend: + service: + name: kandev + port: + name: backend + - path: /ws + pathType: Prefix + backend: + service: + name: kandev + port: + name: backend + - path: /health + pathType: Exact + backend: + service: + name: kandev + port: + name: backend + # Web UI (catch-all) + - path: / + pathType: Prefix + backend: + service: + name: kandev + port: + name: web diff --git a/k8s/pvc.yaml b/k8s/pvc.yaml new file mode 100644 index 0000000000..887dca8b6a --- /dev/null +++ b/k8s/pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: kandev-data + labels: + app: kandev +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi diff --git a/k8s/service.yaml b/k8s/service.yaml new file mode 100644 index 0000000000..20465f179d --- /dev/null +++ b/k8s/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: kandev + labels: + app: kandev +spec: + type: ClusterIP + selector: + app: kandev + ports: + - name: backend + port: 8080 + targetPort: backend + protocol: TCP + - name: web + port: 3000 + targetPort: web + protocol: TCP