From 69993a3bf5b19051eb57a630a373ef367d6e347a Mon Sep 17 00:00:00 2001 From: Ryan Kazokas Date: Sun, 29 Mar 2026 08:46:27 -0400 Subject: [PATCH] Deploy to production: GitHub Actions + ghcr.io + Kubernetes - Switch from Gitea to GitHub Container Registry (ghcr.io) - Add GitHub Actions workflow with Tailscale connectivity - Update k8s manifests for cloud nodes and Traefik ingress - Configure for turbo.kazcloud.dev domain - Test deployment with home page text change --- .../workflows/build-and-deploy.yml | 49 +-- Dockerfile | 43 ++- INITIAL_DEPLOYMENT.md | 320 +++++++++++++++++ README.md | 19 +- TODO.md | 130 +++++++ app/views/pages/home.html.erb | 2 +- docs/GITEA_SECRETS.md | 202 ----------- docs/GITEA_WORKFLOW.md | 323 ------------------ docs/GITHUB_SECRETS.md | 251 ++++++++++++++ k8s/configmap.yaml | 7 +- k8s/deployment.yaml | 14 +- k8s/ingress.yaml | 15 +- k8s/migrate-job.yaml | 12 +- scripts/update-deployment.sh | 2 +- 14 files changed, 793 insertions(+), 596 deletions(-) rename {.gitea => .github}/workflows/build-and-deploy.yml (68%) create mode 100644 INITIAL_DEPLOYMENT.md create mode 100644 TODO.md delete mode 100644 docs/GITEA_SECRETS.md delete mode 100644 docs/GITEA_WORKFLOW.md create mode 100644 docs/GITHUB_SECRETS.md diff --git a/.gitea/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml similarity index 68% rename from .gitea/workflows/build-and-deploy.yml rename to .github/workflows/build-and-deploy.yml index 6877a00..68831a2 100644 --- a/.gitea/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -14,9 +14,6 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Extract version from tag id: version run: | @@ -28,12 +25,15 @@ jobs: echo "tag=latest" >> $GITHUB_OUTPUT fi - - name: Log in to Gitea Container Registry + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: - registry: gitea.kazcloud.dev + registry: ghcr.io username: ${{ github.actor }} - password: ${{ secrets.GITEA_TOKEN }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push Docker image uses: docker/build-push-action@v5 @@ -41,32 +41,43 @@ jobs: context: . push: true tags: | - gitea.kazcloud.dev/ryankazokas/turbovault-app:${{ steps.version.outputs.tag }} - gitea.kazcloud.dev/ryankazokas/turbovault-app:latest + ghcr.io/ryankazokas/turbovault-app:${{ steps.version.outputs.tag }} + ghcr.io/ryankazokas/turbovault-app:latest cache-from: type=gha cache-to: type=gha,mode=max - - name: Deploy to Kubernetes + - name: Connect to Tailscale + uses: tailscale/github-action@v2 + with: + oauth-client-id: ${{ secrets.TAILSCALE_CLIENT_ID }} + oauth-secret: ${{ secrets.TAILSCALE_CLIENT_SECRET }} + tags: tag:ci + + - name: Setup kubectl + uses: azure/setup-kubectl@v3 + + - name: Configure kubeconfig env: KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }} run: | - # Setup kubectl mkdir -p ~/.kube echo "$KUBECONFIG_CONTENT" | base64 -d > ~/.kube/config chmod 600 ~/.kube/config - - # Deploy + + - name: Deploy to Kubernetes + run: | echo "🚀 Deploying version ${{ steps.version.outputs.tag }} to Kubernetes..." + kubectl set image deployment/turbovault \ - turbovault=gitea.kazcloud.dev/ryankazokas/turbovault-app:${{ steps.version.outputs.tag }} \ + turbovault=ghcr.io/ryankazokas/turbovault-app:${{ steps.version.outputs.tag }} \ -n turbovault - # Wait for rollout + echo "⏳ Waiting for rollout to complete..." kubectl rollout status deployment/turbovault -n turbovault --timeout=5m echo "✅ Deployment complete!" - - # Show current pods + echo "" + echo "📊 Current pods:" kubectl get pods -n turbovault -l app=turbovault - name: Deployment summary @@ -74,7 +85,7 @@ jobs: run: | echo "✅ Build and deployment successful!" echo "" - echo "📦 Image: gitea.kazcloud.dev/ryankazokas/turbovault-app:${{ steps.version.outputs.tag }}" + echo "📦 Image: ghcr.io/ryankazokas/turbovault-app:${{ steps.version.outputs.tag }}" echo "🚀 Deployed to: turbovault namespace" echo "" echo "View logs:" @@ -85,8 +96,8 @@ jobs: env: KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }} run: | - echo "❌ Deployment failed! Rolling back..." + echo "❌ Deployment failed! Attempting rollback..." mkdir -p ~/.kube echo "$KUBECONFIG_CONTENT" | base64 -d > ~/.kube/config kubectl rollout undo deployment/turbovault -n turbovault || true - echo "⚠️ Attempted rollback. Check cluster status manually." + echo "⚠️ Rollback attempted. Check cluster status manually." diff --git a/Dockerfile b/Dockerfile index c075e23..c6767ab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,42 +1,62 @@ # TurboVault Production Dockerfile # Multi-stage build for optimized image size -# Stage 1: Build environment -FROM ruby:3.3-slim as builder +# Stage 1: Builder - Full environment with all gems +FROM ruby:3.3-slim AS builder # Install build dependencies RUN apt-get update -qq && \ apt-get install -y --no-install-recommends \ build-essential \ libpq-dev \ + libyaml-dev \ nodejs \ npm \ git \ curl && \ rm -rf /var/lib/apt/lists/* -# Set working directory WORKDIR /app -# Install gems +# Install ALL gems first (needed for asset compilation) COPY Gemfile Gemfile.lock ./ -RUN bundle config set --local deployment 'true' && \ - bundle config set --local without 'development test' && \ - bundle install --jobs 4 --retry 3 +RUN bundle install --jobs 4 --retry 3 # Copy application code COPY . . # Precompile assets +ENV RAILS_ENV=production \ + NODE_ENV=production \ + SECRET_KEY_BASE=dummy RUN bundle exec rails assets:precompile -# Stage 2: Runtime environment +# Stage 2: Production gems only +FROM ruby:3.3-slim AS gems + +# Install build dependencies (needed to install gems with native extensions) +RUN apt-get update -qq && \ + apt-get install -y --no-install-recommends \ + build-essential \ + libpq-dev \ + libyaml-dev && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Install ONLY production gems +COPY Gemfile Gemfile.lock ./ +RUN bundle config set --local without 'development test' && \ + bundle install --jobs 4 --retry 3 + +# Stage 3: Runtime - Minimal final image FROM ruby:3.3-slim -# Install runtime dependencies +# Install ONLY runtime dependencies (no build tools!) RUN apt-get update -qq && \ apt-get install -y --no-install-recommends \ libpq5 \ + libyaml-0-2 \ curl \ ca-certificates && \ rm -rf /var/lib/apt/lists/* @@ -44,11 +64,10 @@ RUN apt-get update -qq && \ # Create app user RUN groupadd -r app && useradd -r -g app app -# Set working directory WORKDIR /app -# Copy gems from builder -COPY --from=builder /usr/local/bundle /usr/local/bundle +# Copy production gems from gems stage +COPY --from=gems /usr/local/bundle /usr/local/bundle # Copy application code COPY --chown=app:app . . diff --git a/INITIAL_DEPLOYMENT.md b/INITIAL_DEPLOYMENT.md new file mode 100644 index 0000000..1386d9f --- /dev/null +++ b/INITIAL_DEPLOYMENT.md @@ -0,0 +1,320 @@ +# Initial Deployment Guide + +Follow these steps to deploy TurboVault to Kubernetes for the first time. + +## Prerequisites + +- ✅ Code pushed to GitHub +- ✅ PostgreSQL database ready (host, user, password) +- ✅ kubectl configured for k3s cluster (100.101.31.99:6443) +- ✅ Docker installed locally +- ✅ Gitea account at gitea.kazcloud.dev + +--- + +## Step 1: Build and Push Initial Image + +Since GitHub Actions hasn't run yet, build the first image manually: + +```bash +# Build image +docker build -t gitea.kazcloud.dev/ryan/turbovault-app:v1.0.0 . + +# Login to Gitea registry +docker login gitea.kazcloud.dev +# Username: ryankazokas +# Password: + +# Push image +docker push gitea.kazcloud.dev/ryan/turbovault-app:v1.0.0 + +# Also tag as latest +docker tag gitea.kazcloud.dev/ryan/turbovault-app:v1.0.0 \ + gitea.kazcloud.dev/ryan/turbovault-app:latest +docker push gitea.kazcloud.dev/ryan/turbovault-app:latest +``` + +✅ **Verify:** Check Gitea packages at `gitea.kazcloud.dev/ryankazokas/-/packages` + +--- + +## Step 2: Configure Kubernetes Secrets + +```bash +# Copy template +cp k8s/secrets.yaml.example k8s/secrets.yaml + +# Generate Rails secret key +rails secret +# Copy the output + +# Edit secrets file +nano k8s/secrets.yaml +``` + +**Add these values in `k8s/secrets.yaml`:** + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: turbovault-secrets + namespace: turbovault +type: Opaque +stringData: + SECRET_KEY_BASE: "" + DATABASE_PASSWORD: "your-postgres-password" + + # Optional: IGDB integration + IGDB_CLIENT_ID: "your-igdb-client-id" + IGDB_CLIENT_SECRET: "your-igdb-client-secret" + + # Optional: Email (for password resets) + SMTP_ADDRESS: "smtp.example.com" + SMTP_PORT: "587" + SMTP_USERNAME: "user@example.com" + SMTP_PASSWORD: "smtp-password" +``` + +--- + +## Step 3: Configure Database Connection + +Edit `k8s/configmap.yaml`: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: turbovault-config + namespace: turbovault +data: + RAILS_ENV: "production" + DATABASE_HOST: "your-postgres-host" + DATABASE_NAME: "turbovault_production" + DATABASE_USERNAME: "turbovault" + RAILS_LOG_TO_STDOUT: "true" + RAILS_SERVE_STATIC_FILES: "true" +``` + +**Replace:** +- `your-postgres-host` - Your PostgreSQL server hostname/IP +- Database name and username if different + +--- + +## Step 4: Deploy to Kubernetes + +Run the automated deployment script: + +```bash +./scripts/deploy-k8s.sh +``` + +**When prompted for registry credentials:** +- Registry: `gitea.kazcloud.dev` +- Username: `ryankazokas` +- Password: `` + +**The script will:** +1. Create namespace +2. Apply configmap +3. Apply secrets +4. Run database migration job +5. Deploy application +6. Create service +7. Create ingress + +--- + +## Step 5: Verify Deployment + +```bash +# Check all resources +kubectl get all -n turbovault + +# Check pods are running +kubectl get pods -n turbovault + +# View logs +kubectl logs -f -l app=turbovault -n turbovault + +# Check migration job completed +kubectl logs job/turbovault-migrate -n turbovault +``` + +**Expected output:** +``` +NAME READY STATUS RESTARTS AGE +pod/turbovault-xxxxxxxxxx-xxxxx 1/1 Running 0 2m +pod/turbovault-xxxxxxxxxx-xxxxx 1/1 Running 0 2m +``` + +--- + +## Step 6: Access Application + +### Option A: Port Forward (Testing) + +```bash +kubectl port-forward svc/turbovault-service 3000:80 -n turbovault +``` + +Visit: http://localhost:3000 + +### Option B: Ingress (Production) + +Edit `k8s/ingress.yaml` with your domain and apply: + +```bash +kubectl apply -f k8s/ingress.yaml +``` + +Visit: https://your-domain.com + +--- + +## Step 7: Configure GitHub Secrets + +Now set up automated deployments for future updates. + +See [docs/GITHUB_SECRETS.md](docs/GITHUB_SECRETS.md) for detailed instructions. + +**Required secrets (add at https://github.com/ryankazokas/turbovault-app/settings/secrets/actions):** + +1. `GITEA_USERNAME` - Your Gitea username +2. `GITEA_TOKEN` - Gitea access token (Settings → Applications) +3. `TAILSCALE_CLIENT_ID` - Tailscale OAuth client ID +4. `TAILSCALE_CLIENT_SECRET` - Tailscale OAuth client secret +5. `KUBECONFIG` - Base64-encoded kubeconfig (`cat ~/.kube/config | base64 -w 0`) + +--- + +## Step 8: Test Automated Deployment + +After GitHub secrets are configured: + +```bash +# Create a test tag +git tag v1.0.1 +git push origin v1.0.1 +``` + +Watch at: https://github.com/ryankazokas/turbovault-app/actions + +GitHub Actions will: +1. Build Docker image +2. Push to Gitea registry +3. Connect via Tailscale +4. Deploy to Kubernetes + +--- + +## Troubleshooting + +### Pods in CrashLoopBackOff + +```bash +# View logs +kubectl logs -l app=turbovault -n turbovault + +# Common issues: +# - Database connection failed (check configmap/secrets) +# - Missing SECRET_KEY_BASE (check secrets) +# - Migration not run (check migration job logs) +``` + +### Migration Job Failed + +```bash +# View migration logs +kubectl logs job/turbovault-migrate -n turbovault + +# Re-run migration +kubectl delete job turbovault-migrate -n turbovault +kubectl apply -f k8s/migrate-job.yaml +``` + +### Can't Pull Image + +```bash +# Check image pull secret +kubectl get secrets -n turbovault + +# Re-run deploy script to create secret +./scripts/deploy-k8s.sh +``` + +### Database Connection Failed + +```bash +# Test from pod +kubectl exec -it deployment/turbovault -n turbovault -- \ + rails runner "puts ActiveRecord::Base.connection.execute('SELECT 1').first" + +# Check environment variables +kubectl exec -it deployment/turbovault -n turbovault -- env | grep DATABASE +``` + +--- + +## Next Steps + +After successful initial deployment: + +1. ✅ Application is running in Kubernetes +2. ✅ GitHub Actions configured for automated deployments +3. 🚀 **Daily workflow:** Just push tags to deploy! + +```bash +# Make changes +git add . +git commit -m "Feature: new functionality" +git push + +# Deploy +git tag v1.0.2 +git push origin v1.0.2 + +# GitHub Actions automatically builds and deploys! ✅ +``` + +--- + +## Quick Reference + +```bash +# View status +kubectl get all -n turbovault + +# View logs +kubectl logs -f -l app=turbovault -n turbovault + +# Restart deployment +kubectl rollout restart deployment/turbovault -n turbovault + +# Rollback deployment +kubectl rollout undo deployment/turbovault -n turbovault + +# Delete everything (start over) +kubectl delete namespace turbovault +``` + +--- + +## Files You Modified + +Keep these files safe (they're gitignored): + +- `k8s/secrets.yaml` - Contains sensitive data (SECRET_KEY_BASE, passwords) +- `~/.kube/config` - Your Kubernetes access + +**DO NOT** commit these to git! + +--- + +**Need help?** Check logs first: +```bash +kubectl describe pod -l app=turbovault -n turbovault +kubectl logs -l app=turbovault -n turbovault +``` diff --git a/README.md b/README.md index 8df7c7f..3b93afa 100644 --- a/README.md +++ b/README.md @@ -38,19 +38,14 @@ Visit http://localhost:3000 ### Deploy to Kubernetes -```bash -# 1. Build & push image -git tag v1.0.0 && git push origin v1.0.0 +See [INITIAL_DEPLOYMENT.md](INITIAL_DEPLOYMENT.md) for complete setup guide. -# 2. Configure secrets -cp k8s/secrets.yaml.example k8s/secrets.yaml -# Edit with your values - -# 3. Deploy -./scripts/deploy-k8s.sh -``` - -See [docs/QUICK_START.md](docs/QUICK_START.md) for details. +**Quick steps:** +1. Build and push first image to Gitea registry +2. Configure k8s secrets and configmap +3. Run `./scripts/deploy-k8s.sh` +4. Configure GitHub Secrets for automation +5. Push tags to deploy: `git tag v1.0.1 && git push origin v1.0.1` ## Tech Stack diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..872ee8e --- /dev/null +++ b/TODO.md @@ -0,0 +1,130 @@ +# TurboVault TODO + +## Fix Gitea Ingress for Large Container Pushes + +**Issue:** Docker push to Gitea registry times out on large layers (~250MB) + +**Error:** +``` +Package registry API internal error: 500 unexpected EOF +``` + +**Root Cause:** Ingress controller (nginx/traefik) in front of Gitea has timeout limits that prevent large uploads from completing. + +**Symptoms:** +- Small layers push fine (already exists) +- Large gem layer (12f753f9ec10, ~250MB) times out after ~1 minute +- Gitea logs show: `PATCH /v2/ryan/turbovault-app/blobs/uploads/... elapsed 3275.4ms ... 500 unexpected EOF` + +### Solution: Update Gitea Ingress + +Find your Gitea ingress configuration and add these annotations: + +**For NGINX Ingress Controller:** +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: gitea + namespace: tools # or wherever gitea is + annotations: + # Allow unlimited upload size + nginx.ingress.kubernetes.io/proxy-body-size: "0" + + # Increase timeouts to 10 minutes + nginx.ingress.kubernetes.io/proxy-read-timeout: "600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "600" + nginx.ingress.kubernetes.io/proxy-connect-timeout: "600" + + # Enable chunked uploads + nginx.ingress.kubernetes.io/proxy-request-buffering: "off" +``` + +**For Traefik Ingress:** +```yaml +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: gitea-buffering + namespace: tools +spec: + buffering: + maxRequestBodyBytes: 0 # Unlimited + memRequestBodyBytes: 2097152 # 2MB in memory, rest to disk + +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: gitea + namespace: tools + annotations: + traefik.ingress.kubernetes.io/router.middlewares: tools-gitea-buffering@kubernetescrd +``` + +### Steps to Fix: + +1. **Find current Gitea ingress:** + ```bash + kubectl get ingress -n tools + kubectl get ingress gitea -n tools -o yaml + ``` + +2. **Identify ingress controller:** + ```bash + kubectl get ingressclass + # Check which controller: nginx, traefik, etc. + ``` + +3. **Update ingress with appropriate annotations** (see above) + +4. **Apply changes:** + ```bash + kubectl apply -f .yaml + ``` + +5. **Test push:** + ```bash + docker push gitea.kazcloud.dev/ryan/turbovault-app:v1.0.0 + ``` + +### Alternative: Increase Gitea Service Timeouts + +If using a LoadBalancer or NodePort directly: + +Edit Gitea's `app.ini`: +```ini +[server] +LFS_MAX_FILE_SIZE = 0 +HTTP_PORT = 3000 + +[packages] +ENABLED = true +CHUNKED_UPLOAD_PATH = /tmp/package-upload +LIMIT_TOTAL_OWNER_SIZE = -1 +LIMIT_SIZE_CONTAINER = -1 +``` + +Then restart Gitea pod. + +--- + +## Current Workaround + +Using GitHub Container Registry (ghcr.io) for now: +- Image: `ghcr.io/ryankazokas/turbovault-app:latest` +- All k8s manifests updated to use ghcr.io +- GitHub Actions workflow configured +- Works perfectly, no timeout issues + +Once Gitea ingress is fixed, can switch back by updating: +- k8s/deployment.yaml +- k8s/migrate-job.yaml +- .github/workflows/build-and-deploy.yml +- scripts/update-deployment.sh + +--- + +**Priority:** Low (ghcr.io works fine for now) +**Complexity:** Medium (depends on ingress controller setup) +**Benefit:** Full control over container registry on your infrastructure diff --git a/app/views/pages/home.html.erb b/app/views/pages/home.html.erb index 94b8fc0..0d0791a 100644 --- a/app/views/pages/home.html.erb +++ b/app/views/pages/home.html.erb @@ -1,6 +1,6 @@

Welcome to TurboVault

-

Track and manage your video game collection with ease

+

Track and manage your video game collection with ease (Test Deployment v1.0.1)

<%= link_to "Get Started", signup_path, class: "px-8 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 text-lg font-semibold" %> diff --git a/docs/GITEA_SECRETS.md b/docs/GITEA_SECRETS.md deleted file mode 100644 index 0f6bdc5..0000000 --- a/docs/GITEA_SECRETS.md +++ /dev/null @@ -1,202 +0,0 @@ -# Gitea Secrets Configuration - -This document explains what secrets you need to configure in Gitea for automatic builds and deployments. - -## Required Secrets - -### 1. GITEA_TOKEN - -**Purpose:** Allows Gitea Actions to push Docker images to Gitea Container Registry - -**How to create:** - -1. Go to Gitea → **Settings** → **Applications** -2. Under **"Generate New Token"**, enter name: `gitea-actions` -3. Select scopes: - - ✅ `write:package` (push container images) - - ✅ `read:package` (pull container images) -4. Click **"Generate Token"** -5. Copy the token (starts with `glpat-...` or similar) - -**How to add to repository:** - -1. Go to your Gitea repository: `gitea.kazcloud.dev/ryankazokas/turbovault-app` -2. Click **Settings** → **Secrets** -3. Click **"Add Secret"** -4. Name: `GITEA_TOKEN` -5. Value: Paste the token you copied -6. Click **"Add Secret"** - ---- - -### 2. KUBECONFIG - -**Purpose:** Allows Gitea Actions to deploy to your Kubernetes cluster - -**How to create:** - -```bash -# Export your kubeconfig as base64 -cat ~/.kube/config | base64 -w 0 > kubeconfig-base64.txt - -# Copy the contents of kubeconfig-base64.txt -cat kubeconfig-base64.txt -``` - -**How to add to repository:** - -1. Go to your Gitea repository: `gitea.kazcloud.dev/ryankazokas/turbovault-app` -2. Click **Settings** → **Secrets** -3. Click **"Add Secret"** -4. Name: `KUBECONFIG` -5. Value: Paste the base64-encoded kubeconfig -6. Click **"Add Secret"** - -**⚠️ Security Note:** This gives Gitea Actions full access to your Kubernetes cluster. Only add this to trusted repositories! - ---- - -## Optional: Scoped Kubeconfig (More Secure) - -Instead of using your full kubeconfig, create a limited service account: - -```bash -# Create service account for deployments -kubectl create serviceaccount turbovault-deployer -n turbovault - -# Create role with deployment permissions -cat < token.txt - -# Create minimal kubeconfig -cat < deployer-kubeconfig.yaml -apiVersion: v1 -kind: Config -clusters: -- cluster: - server: https://100.101.31.99:6443 - # Add certificate-authority-data from your main kubeconfig if needed - insecure-skip-tls-verify: true - name: k3s -contexts: -- context: - cluster: k3s - namespace: turbovault - user: turbovault-deployer - name: k3s -current-context: k3s -users: -- name: turbovault-deployer - user: - token: $(cat token.txt) -EOF - -# Encode for Gitea -cat deployer-kubeconfig.yaml | base64 -w 0 > deployer-kubeconfig-base64.txt - -# Use this in KUBECONFIG secret instead -cat deployer-kubeconfig-base64.txt -``` - -This limits Gitea Actions to only deploying TurboVault, not full cluster access. - ---- - -## Verifying Secrets - -After adding secrets, you can verify they're set: - -1. Go to repository → **Settings** → **Secrets** -2. You should see: - - `GITEA_TOKEN` ✅ - - `KUBECONFIG` ✅ - -**Note:** You can't view secret values after creation (security feature). - ---- - -## Testing the Workflow - -After secrets are configured: - -```bash -# Create a test tag -git tag v0.0.1-test -git push origin v0.0.1-test -``` - -Watch the workflow at: -`gitea.kazcloud.dev/ryankazokas/turbovault-app/actions` - -The workflow should: -1. ✅ Build Docker image -2. ✅ Push to Gitea registry -3. ✅ Deploy to Kubernetes -4. ✅ Wait for rollout to complete - ---- - -## Troubleshooting - -### "Error: authentication required" -- Check `GITEA_TOKEN` is set and has `write:package` scope - -### "Error: Unable to connect to the server" -- Check `KUBECONFIG` secret is set correctly -- Verify base64 encoding (no line breaks with `-w 0`) -- Test kubeconfig works locally: `kubectl --kubeconfig= get pods -n turbovault` - -### "Error: deployment not found" -- Make sure initial deployment is done first: `./scripts/deploy-k8s.sh` -- Workflow only updates existing deployments, doesn't create them - ---- - -## Security Best Practices - -✅ **DO:** -- Use service account with minimal permissions (Role, not ClusterRole) -- Rotate tokens regularly -- Only add secrets to repositories you control - -❌ **DON'T:** -- Share secrets in code or documentation -- Use admin kubeconfig if possible -- Commit secrets to git - ---- - -## Summary - -**Two secrets required:** - -1. **GITEA_TOKEN** - For pushing container images -2. **KUBECONFIG** - For deploying to Kubernetes - -Both added at: `gitea.kazcloud.dev/ryankazokas/turbovault-app/settings/secrets` - -After setup, just push tags to trigger automatic builds and deployments! 🚀 diff --git a/docs/GITEA_WORKFLOW.md b/docs/GITEA_WORKFLOW.md deleted file mode 100644 index 1d4d35f..0000000 --- a/docs/GITEA_WORKFLOW.md +++ /dev/null @@ -1,323 +0,0 @@ -# Gitea CI/CD Workflow - -## Overview - -TurboVault uses Gitea Actions for fully automated builds and deployments: - -1. **Push code to GitHub** (primary repository) -2. **Gitea mirrors** from GitHub automatically -3. **Gitea Actions** builds Docker image and deploys to Kubernetes - -All automatic! 🚀 - ---- - -## Setup (One-Time) - -### 1. Mirror GitHub Repository to Gitea - -In Gitea: - -1. Go to `gitea.kazcloud.dev` → **New Repository** -2. Choose **"New Migration"** → **"GitHub"** -3. URL: `https://github.com/ryankazokas/turbovault-app` -4. Enable **"This repository will be a mirror"** -5. Set sync interval: **10 minutes** (or setup webhook for instant sync) -6. Click **"Migrate Repository"** - -**Webhook for instant sync (optional):** -- In GitHub repo → Settings → Webhooks → Add webhook -- Payload URL: `https://gitea.kazcloud.dev/ryankazokas/turbovault-app/mirror-sync` -- Content type: `application/json` -- Events: Just the push event - -### 2. Configure Gitea Secrets - -See [GITEA_SECRETS.md](GITEA_SECRETS.md) for detailed instructions. - -**Required secrets:** -- `GITEA_TOKEN` - For pushing images to registry -- `KUBECONFIG` - For deploying to Kubernetes - -Add at: `gitea.kazcloud.dev/ryankazokas/turbovault-app/settings/secrets` - -### 3. Initial Deployment to Kubernetes - -Before automation works, do initial deployment: - -```bash -# Build and push first image -docker build -t gitea.kazcloud.dev/ryankazokas/turbovault-app:v1.0.0 . -docker login gitea.kazcloud.dev -docker push gitea.kazcloud.dev/ryankazokas/turbovault-app:v1.0.0 - -# Configure secrets and database -cp k8s/secrets.yaml.example k8s/secrets.yaml -nano k8s/secrets.yaml # Add SECRET_KEY_BASE, DATABASE_PASSWORD, etc. -nano k8s/configmap.yaml # Add DATABASE_HOST, etc. - -# Deploy to Kubernetes -./scripts/deploy-k8s.sh -``` - -When script asks for registry credentials: -- Registry: `gitea.kazcloud.dev` -- Username: `ryankazokas` -- Password: `` - ---- - -## Daily Workflow - -After setup, deployments are fully automatic: - -```bash -# 1. Make changes in GitHub -git add . -git commit -m "Feature: add new functionality" -git push origin main - -# 2. When ready to deploy, create version tag -git tag v1.0.1 -git push origin v1.0.1 - -# 3. That's it! ✅ -# Gitea auto-syncs → builds → deploys to Kubernetes -``` - -**Watch build and deployment:** -`https://gitea.kazcloud.dev/ryankazokas/turbovault-app/actions` - ---- - -## How It Works - -``` -GitHub (code) - ↓ (mirror sync) -Gitea (code mirror) - ↓ (on tag push) -Gitea Actions: - 1. Build Docker image - 2. Push to gitea.kazcloud.dev registry - 3. Deploy to Kubernetes (kubectl) - 4. Wait for rollout - 5. Show status - ↓ -Kubernetes pulls image and updates deployment ✅ -``` - ---- - -## Workflow Features - -### ✅ Automatic Rollout Status -Workflow waits for rollout to complete before marking as success. - -### ✅ Automatic Rollback on Failure -If deployment fails, workflow automatically rolls back to previous version. - -### ✅ Multiple Tags -Each version gets two tags: -- `v1.0.0` (specific version) -- `latest` (always points to most recent) - -### ✅ Manual Trigger -Can manually trigger builds via Gitea Actions UI if needed. - ---- - -## Manual Deployment (If Needed) - -If you want to deploy manually without waiting for Gitea sync: - -```bash -# Build and push -docker build -t gitea.kazcloud.dev/ryankazokas/turbovault-app:v1.0.1 . -docker push gitea.kazcloud.dev/ryankazokas/turbovault-app:v1.0.1 - -# Deploy -./scripts/update-deployment.sh v1.0.1 -``` - ---- - -## Version Management - -### Semantic Versioning - -Use semantic versioning for tags: -- `v1.0.0` - Major.Minor.Patch -- `v1.0.1` - Patch update (bug fixes) -- `v1.1.0` - Minor update (new features) -- `v2.0.0` - Major update (breaking changes) - -### Viewing Deployed Version - -```bash -# Check current image -kubectl get deployment turbovault -n turbovault -o jsonpath='{.spec.template.spec.containers[0].image}' - -# Check all pods -kubectl get pods -n turbovault -l app=turbovault -``` - ---- - -## Troubleshooting - -### Workflow Fails at "Build and push" - -**Issue:** Can't push to registry - -**Fix:** -- Check `GITEA_TOKEN` secret is set -- Verify token has `write:package` scope -- Test: `docker login gitea.kazcloud.dev` with token - -### Workflow Fails at "Deploy to Kubernetes" - -**Issue:** Can't connect to Kubernetes - -**Fix:** -- Check `KUBECONFIG` secret is set correctly -- Verify base64 encoding: `echo "$SECRET" | base64 -d | kubectl --kubeconfig=/dev/stdin get nodes` -- Check cluster is reachable from Gitea Actions runner - -### Deployment Succeeds but Pods Not Starting - -**Issue:** Image pull errors or configuration issues - -**Check:** -```bash -kubectl get pods -n turbovault -kubectl describe pod -n turbovault -kubectl logs -n turbovault -``` - -**Common causes:** -- Image pull secret not configured (run `./scripts/deploy-k8s.sh` again) -- Database connection issues (check `k8s/configmap.yaml` and `k8s/secrets.yaml`) -- Missing environment variables - ---- - -## Monitoring Deployments - -### Watch Live Deployment - -```bash -# Watch pods update -kubectl get pods -n turbovault -w - -# Watch rollout status -kubectl rollout status deployment/turbovault -n turbovault - -# View logs -kubectl logs -f -l app=turbovault -n turbovault -``` - -### Deployment History - -```bash -# View rollout history -kubectl rollout history deployment/turbovault -n turbovault - -# View specific revision -kubectl rollout history deployment/turbovault --revision=2 -n turbovault -``` - ---- - -## Rollback - -### Automatic Rollback -Workflow automatically rolls back if deployment fails. - -### Manual Rollback - -```bash -# Rollback to previous version -kubectl rollout undo deployment/turbovault -n turbovault - -# Rollback to specific revision -kubectl rollout undo deployment/turbovault --to-revision=3 -n turbovault -``` - ---- - -## Security Considerations - -### Secrets Management -- All secrets in Gitea are encrypted -- Secrets never appear in logs -- Use service account kubeconfig (not admin) - -### Network Security -- Gitea Actions runners on internal network -- Can reach Kubernetes API (not exposed publicly) -- Images pulled from internal registry - -### Access Control -- Only repository collaborators can trigger workflows -- Gitea token scoped to package registry only -- Kubeconfig scoped to turbovault namespace only (recommended) - ---- - -## Advanced: Staging vs Production - -To add staging environment: - -1. Create `v*.*.*-rc*` tags for release candidates -2. Deploy to staging namespace -3. Update workflow to detect RC tags: - -```yaml -- name: Determine environment - id: env - run: | - if [[ "${{ github.ref }}" =~ -rc ]]; then - echo "namespace=turbovault-staging" >> $GITHUB_OUTPUT - else - echo "namespace=turbovault" >> $GITHUB_OUTPUT - fi -``` - ---- - -## Quick Reference - -```bash -# Deploy new version -git tag v1.0.1 -git push origin v1.0.1 - -# Watch deployment -kubectl get pods -n turbovault -w - -# View logs -kubectl logs -f -l app=turbovault -n turbovault - -# Rollback if needed -kubectl rollout undo deployment/turbovault -n turbovault - -# Check current version -kubectl get deployment turbovault -n turbovault -o jsonpath='{.spec.template.spec.containers[0].image}' -``` - ---- - -## Summary - -**Setup once:** -1. Mirror GitHub → Gitea -2. Add Gitea secrets (GITEA_TOKEN, KUBECONFIG) -3. Initial deployment with `./scripts/deploy-k8s.sh` - -**Daily workflow:** -1. Push code to GitHub -2. Create version tag -3. Everything else is automatic! ✅ - -Questions? Check [GITEA_SECRETS.md](GITEA_SECRETS.md) for secret configuration details. diff --git a/docs/GITHUB_SECRETS.md b/docs/GITHUB_SECRETS.md new file mode 100644 index 0000000..c4ff0df --- /dev/null +++ b/docs/GITHUB_SECRETS.md @@ -0,0 +1,251 @@ +# GitHub Secrets Configuration + +This document explains what secrets you need to configure in GitHub for automatic builds and deployments. + +## Required Secrets + +Go to: **`https://github.com/ryan/turbovault-app/settings/secrets/actions`** + +### 1. GITHUB_TOKEN (Built-in) + +**Purpose:** Authenticate to GitHub Container Registry (ghcr.io) + +**Value:** This is automatically provided by GitHub Actions - no setup needed! + +**How to add:** +1. Go to GitHub repo → Settings → Secrets and variables → Actions +2. Click **"New repository secret"** +3. Name: `GITEA_USERNAME` +4. Secret: `ryan` +5. Click **"Add secret"** + +--- + +### 2. TAILSCALE_CLIENT_ID + +**Purpose:** Allows GitHub Actions to connect to your Tailscale network (to reach Kubernetes) + +**How to create:** + +1. Go to: https://login.tailscale.com/admin/settings/oauth +2. Click **"Generate OAuth client"** +3. Description: `GitHub Actions - TurboVault` +4. Select tags: **`tag:ci`** (or create if it doesn't exist) +5. Copy the **Client ID** + +**How to add to GitHub:** + +1. Go to GitHub repo → Settings → Secrets and variables → Actions +2. Click **"New repository secret"** +3. Name: `TAILSCALE_CLIENT_ID` +4. Secret: Paste the Client ID +5. Click **"Add secret"** + +--- + +### 3. TAILSCALE_CLIENT_SECRET + +**Purpose:** OAuth secret for Tailscale connection + +**How to get:** + +(You got this when creating the OAuth client in step 3 above) + +**How to add to GitHub:** + +1. Go to GitHub repo → Settings → Secrets and variables → Actions +2. Click **"New repository secret"** +3. Name: `TAILSCALE_CLIENT_SECRET` +4. Secret: Paste the Client Secret +5. Click **"Add secret"** + +--- + +### 4. KUBECONFIG + +**Purpose:** Allows kubectl to deploy to your Kubernetes cluster + +**How to create:** + +```bash +# Encode your kubeconfig as base64 +cat ~/.kube/config | base64 -w 0 > kubeconfig-base64.txt + +# Copy the contents +cat kubeconfig-base64.txt +``` + +**How to add to GitHub:** + +1. Go to GitHub repo → Settings → Secrets and variables → Actions +2. Click **"New repository secret"** +3. Name: `KUBECONFIG` +4. Secret: Paste the base64-encoded kubeconfig +5. Click **"Add secret"** + +--- + +## Optional: Scoped Kubeconfig (More Secure) + +Instead of using your full kubeconfig, create a limited service account: + +```bash +# Create service account +kubectl create serviceaccount turbovault-deployer -n turbovault + +# Create role with deployment permissions only +cat < token.txt + +# Create minimal kubeconfig +cat < deployer-kubeconfig.yaml +apiVersion: v1 +kind: Config +clusters: +- cluster: + server: https://100.101.31.99:6443 + insecure-skip-tls-verify: true + name: k3s +contexts: +- context: + cluster: k3s + namespace: turbovault + user: turbovault-deployer + name: k3s +current-context: k3s +users: +- name: turbovault-deployer + user: + token: $(cat token.txt) +EOF + +# Encode for GitHub +cat deployer-kubeconfig.yaml | base64 -w 0 > deployer-kubeconfig-base64.txt + +# Use this in KUBECONFIG secret +cat deployer-kubeconfig-base64.txt +``` + +This limits GitHub Actions to only deploying TurboVault, not full cluster access. + +--- + +## Summary of Required Secrets + +| Secret Name | Purpose | How to Get | +|-------------|---------|------------| +| `GITHUB_TOKEN` | Push to ghcr.io | Built-in (no setup needed!) | +| `TAILSCALE_CLIENT_ID` | Connect to Tailscale | Tailscale → OAuth clients | +| `TAILSCALE_CLIENT_SECRET` | Connect to Tailscale | Tailscale → OAuth clients | +| `KUBECONFIG` | Deploy to Kubernetes | `cat ~/.kube/config \| base64 -w 0` | + +--- + +## Verifying Secrets + +After adding all secrets, you should see 3 secrets in GitHub: + +1. Go to: `https://github.com/ryankazokas/turbovault-app/settings/secrets/actions` +2. Verify all 3 are listed: + - ✅ TAILSCALE_CLIENT_ID + - ✅ TAILSCALE_CLIENT_SECRET + - ✅ KUBECONFIG + +Note: `GITHUB_TOKEN` is automatic - you don't need to add it! + +--- + +## Testing the Workflow + +After secrets are configured: + +```bash +# Create a test tag +git tag v0.0.1-test +git push origin v0.0.1-test +``` + +Watch the workflow at: +`https://github.com/ryan/turbovault-app/actions` + +The workflow should: +1. ✅ Build Docker image +2. ✅ Push to Gitea registry (gitea.kazcloud.dev) +3. ✅ Connect to Tailscale +4. ✅ Deploy to Kubernetes +5. ✅ Wait for rollout to complete + +--- + +## Troubleshooting + +### "Error: authentication required" (Gitea) +- Check `GITEA_TOKEN` is set correctly +- Verify token has `write:package` scope +- Test: `docker login gitea.kazcloud.dev` with token + +### "Error: Failed to connect to Tailscale" +- Check `TAILSCALE_CLIENT_ID` and `TAILSCALE_CLIENT_SECRET` are correct +- Verify OAuth client is active in Tailscale admin +- Check tags are configured correctly (tag:ci) + +### "Error: Unable to connect to the server" (Kubernetes) +- Check `KUBECONFIG` secret is set correctly +- Verify base64 encoding (no line breaks with `-w 0`) +- Verify Tailscale connected (check logs) +- Test kubeconfig works: `kubectl --kubeconfig= get pods -n turbovault` + +### "Error: deployment not found" +- Make sure initial deployment is done first: `./scripts/deploy-k8s.sh` +- Workflow only updates existing deployments, doesn't create them + +--- + +## Security Best Practices + +✅ **DO:** +- Use service account with minimal permissions (recommended) +- Rotate tokens regularly +- Use OAuth for Tailscale (not auth keys) +- Only enable workflows on protected branches + +❌ **DON'T:** +- Share secrets in code or documentation +- Use admin kubeconfig if possible +- Commit secrets to git +- Use long-lived Tailscale auth keys + +--- + +## Next Steps + +After configuring secrets: +1. Read [DEPLOYMENT.md](DEPLOYMENT.md) for initial deployment +2. Test workflow with a tag push +3. Monitor Actions tab for build status + +Questions? Check the GitHub Actions logs for detailed error messages. diff --git a/k8s/configmap.yaml b/k8s/configmap.yaml index 9e9c920..0e36999 100644 --- a/k8s/configmap.yaml +++ b/k8s/configmap.yaml @@ -8,8 +8,7 @@ data: RAILS_LOG_TO_STDOUT: "true" RAILS_SERVE_STATIC_FILES: "true" RAILS_MAX_THREADS: "5" - # Update these values for your environment - DATABASE_HOST: "db.rbeowfzliacsawrziniv.supabase.co" # Your PostgreSQL service name or external host - DATABASE_PORT: "5432" + DATABASE_HOST: "aws-1-us-east-2.pooler.supabase.com" # Your PostgreSQL service name or external host + DATABASE_PORT: "6543" DATABASE_NAME: "postgres" - DATABASE_USERNAME: "postgres" + DATABASE_USERNAME: "postgres.rbeowfzliacsawrziniv" diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index fe261ee..b6915de 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -20,18 +20,20 @@ spec: labels: app: turbovault spec: - # Pull images from container registry - # For private registries, uncomment and create secret: - # imagePullSecrets: - # - name: registry-secret + # Deploy to cloud nodes only + nodeSelector: + node-role: cloud + # Pull images from private GitHub Container Registry + imagePullSecrets: + - name: ghcr-secret containers: - name: turbovault # UPDATE THIS: Replace with your registry path # Examples: - # - Gitea: gitea.kazcloud.dev/ryankazokas/turbovault-app:latest + # - Gitea: gitea.kazcloud.dev/ryan/turbovault-app:latest # - GitHub Container Registry: ghcr.io/ryankazokas/turbovault-app:latest # - Docker Hub: docker.io/username/turbovault:latest - image: gitea.kazcloud.dev/ryankazokas/turbovault-app:latest + image: ghcr.io/ryankazokas/turbovault-app:latest imagePullPolicy: Always ports: - containerPort: 3000 diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml index 7c12d3d..6c77188 100644 --- a/k8s/ingress.yaml +++ b/k8s/ingress.yaml @@ -4,13 +4,13 @@ metadata: name: turbovault-ingress namespace: turbovault annotations: - # Update these based on your ingress controller - # nginx.ingress.kubernetes.io/ssl-redirect: "true" - # cert-manager.io/cluster-issuer: "letsencrypt-prod" + # Use Traefik with Let's Encrypt (same as Gitea) + traefik.ingress.kubernetes.io/router.tls: "true" + traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt spec: - ingressClassName: nginx # Or traefik, depending on your setup + ingressClassName: traefik rules: - - host: turbovault.example.com # Update with your domain + - host: turbo.kazcloud.dev http: paths: - path: / @@ -20,8 +20,3 @@ spec: name: turbovault-service port: number: 80 - # Uncomment for TLS/HTTPS - # tls: - # - hosts: - # - turbovault.example.com - # secretName: turbovault-tls diff --git a/k8s/migrate-job.yaml b/k8s/migrate-job.yaml index a1ec5ba..ac53e7e 100644 --- a/k8s/migrate-job.yaml +++ b/k8s/migrate-job.yaml @@ -7,6 +7,7 @@ metadata: app: turbovault job: migrate spec: + backoffLimit: 3 template: metadata: labels: @@ -14,14 +15,14 @@ spec: job: migrate spec: restartPolicy: OnFailure - # For private registries, uncomment and create secret: - # imagePullSecrets: - # - name: registry-secret + # Pull images from private GitHub Container Registry + imagePullSecrets: + - name: ghcr-secret containers: - name: migrate # UPDATE THIS: Replace with your registry path (same as deployment.yaml) - image: gitea.kazcloud.dev/ryankazokas/turbovault-app:latest - command: ["bundle", "exec", "rails", "db:migrate"] + image: ghcr.io/ryankazokas/turbovault-app:latest + command: ["bin/rails", "db:migrate"] env: # Load from ConfigMap - name: RAILS_ENV @@ -60,4 +61,3 @@ spec: secretKeyRef: name: turbovault-secrets key: SECRET_KEY_BASE - backoffLimit: 3 diff --git a/scripts/update-deployment.sh b/scripts/update-deployment.sh index 5cc7e2c..9995e19 100755 --- a/scripts/update-deployment.sh +++ b/scripts/update-deployment.sh @@ -5,7 +5,7 @@ set -e # Usage: ./scripts/update-deployment.sh v1.0.1 VERSION=${1:-latest} -IMAGE="gitea.kazcloud.dev/ryankazokas/turbovault-app:${VERSION}" +IMAGE="ghcr.io/ryankazokas/turbovault-app:${VERSION}" NAMESPACE="turbovault" echo "🚀 Updating TurboVault deployment to ${VERSION}..."