First Deployment
Overview
Ready to deploy your first application? This guide will walk you through deploying an app to your DineTogether infrastructure using GitOps with FluxCD and Helm.
Prerequisites
- ✅ K3s installed on your server (Server Setup)
- ✅ FluxCD bootstrapped (GitOps Setup)
- ✅ GitHub repository for your app
- ✅ Docker installed locally
- ✅ GitHub CLI installed (
gh)
Step 1: Prepare Your Application
Create a Dockerfile
If you don't have one already:
For Next.js:
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["npm", "start"]
For Django:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["gunicorn", "myapp.wsgi:application", "--bind", "0.0.0.0:8000"]
Step 2: Set Up GitHub Actions
Create .github/workflows/build.yml in your app repository:
name: Build and Push Image
on:
push:
branches: [main]
pull_request:
types: [opened, synchronize]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- 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: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
Step 3: Deploy with Helm
Option A: Use Golden Charts (Recommended)
DineTogether provides pre-configured Helm charts for common app types.
Create a HelmRelease in this repository under clusters/staging/apps/<your-app>/helmrelease.yaml:
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: your-app
namespace: staging
spec:
interval: 5m
releaseName: your-app
chart:
spec:
chart: nextjs-app # or django-app for Django
sourceRef:
kind: GitRepository
name: app-charts
namespace: flux-system
version: "*"
values:
image:
repository: ghcr.io/dine-together/your-app
tag: sha-abc123 # Update with your image tag
pullPolicy: IfNotPresent
ingress:
enabled: true
className: nginx
hosts:
- host: your-app.staging.dinetogether.co.uk
paths:
- path: /
pathType: Prefix
tls:
- secretName: your-app-tls
hosts:
- your-app.staging.dinetogether.co.uk
service:
type: ClusterIP
port: 3000 # 3000 for Next.js, 8000 for Django
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
env:
- name: NODE_ENV
value: production
- name: API_URL
value: https://api.staging.dinetogether.co.uk
# Reference existing secrets
secretRefs:
- name: your-app-secrets # Create this separately
Option B: Use Kustomize
For more control, create Kubernetes manifests directly with Kustomize.
Create the following structure under apps/your-app/:
apps/your-app/
├── base/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ └── kustomization.yaml
└── overlays/
├── staging/
│ └── kustomization.yaml
└── production/
└── kustomization.yaml
Then reference it in clusters/staging/kustomization.yaml:
Step 4: Configure Secrets
Create Kubernetes Secrets
# Create secret for your app
kubectl create secret generic your-app-secrets \
--namespace=staging \
--from-literal=DATABASE_URL=postgresql://... \
--from-literal=API_KEY=your-key-here
For Image Pull Secrets (if repository is private)
# Create GitHub token with read:packages permission
# Then create the secret
kubectl create secret docker-registry ghcr-credentials \
--namespace=staging \
--docker-server=ghcr.io \
--docker-username=YOUR_GITHUB_USERNAME \
--docker-password=YOUR_GITHUB_TOKEN
Step 5: Deploy!
Commit and Push
# Add your HelmRelease file
git add clusters/staging/apps/your-app/helmrelease.yaml
# Commit
git commit -m "Deploy your-app to staging"
# Push to trigger deployment
git push origin main
Monitor Deployment
FluxCD will automatically detect the changes and deploy your app:
# Watch Flux reconciliation
flux get kustomizations --watch
# Check HelmRelease status
flux get helmreleases -n staging
# Watch pods
kubectl get pods -n staging -w
# Check logs
kubectl logs -n staging deployment/your-app
Step 6: Set Up Image Automation (Optional)
To automatically update deployments when new images are pushed:
-
Create an ImageRepository to scan GHCR:
-
Create an ImagePolicy to select tags:
-
Update your HelmRelease to use the marker:
Now Flux will automatically update the tag when new images are pushed!
Verify Deployment
Check Application Status
# Get all resources
kubectl get all -n staging -l app=your-app
# Check ingress
kubectl get ingress -n staging
Access Your Application
Your app will be available at:
SSL certificates are automatically provisioned via cert-manager.
Troubleshooting
Pod Not Starting
# Check pod status
kubectl describe pod -n staging -l app=your-app
# Check events
kubectl get events -n staging --sort-by='.lastTimestamp'
Image Pull Errors
# Verify image exists
docker pull ghcr.io/dine-together/your-app:sha-abc123
# Check image pull secrets
kubectl get secret ghcr-credentials -n staging
Flux Issues
# Check Flux logs
flux logs --follow
# Force reconciliation
flux reconcile helmrelease your-app -n staging
Next Steps
Production Deployment
- Create
clusters/production/apps/your-app/helmrelease.yaml - Update values for production (replicas, resources, domains)
- Use production secrets
- Enable monitoring and alerts
Add Monitoring
Your app automatically gets monitoring if you add Prometheus annotations:
spec:
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3000"
prometheus.io/path: "/metrics"
Configure CI/CD
- Set up staging deployment on merge to main
- Add production deployment with manual approval
- Configure automated testing before deployment
Congratulations! 🎉
You've successfully deployed your application using GitOps!
What You've Accomplished
- ✅ Built and pushed Docker image to GHCR
- ✅ Created HelmRelease for GitOps deployment
- ✅ Deployed to Kubernetes via FluxCD
- ✅ Automatic SSL certificate provisioning
- ✅ Public HTTPS access with monitoring
Key Points to Remember
- GitOps: All changes go through Git
- Helm Charts: Use golden charts for standard apps
- Image Automation: Optional automatic updates
- Monitoring: Built-in Prometheus/Grafana support
Need Help?
- Check Common Issues
- Review GitOps Guide
- See Helm Charts Documentation
Happy deploying! 🚀