NEWS Earn Money with Onidel Cloud! Affiliate Program Details - Check it out

How to Set Up GitOps on a k3s Cluster with Flux v2, SOPS/age Secrets, and OCI Artifacts on Ubuntu 24.04 VPS (2025 Tutorial)

GitOps has revolutionized how teams deploy and manage applications in Kubernetes environments, providing declarative configuration management with Git as the single source of truth. When combined with a lightweight k3s cluster, Flux v2, encrypted secrets management through SOPS/age, and modern OCI artifacts, you get a powerful, production-ready deployment pipeline.

This comprehensive tutorial walks through setting up a complete GitOps workflow on an Ubuntu 24.04 VPS, leveraging Flux v2’s advanced features for automated deployments and secure secrets handling.

Prerequisites

Before starting, ensure you have:

  • Ubuntu 24.04 LTS VPS with at least 4GB RAM and 2 vCPUs
  • Root or sudo access to your server
  • A working k3s cluster deployment
  • Git repository for storing manifests
  • Basic knowledge of Kubernetes and Git workflows

Note: This tutorial assumes you have a functioning k3s cluster. If you need to set one up first, follow our detailed k3s deployment guide.

Step 1: Install Flux CLI

First, install the Flux CLI on your Ubuntu 24.04 system:

# Download and install Flux CLI v2.2.3
curl -s https://fluxcd.io/install.sh | sudo bash

# Verify installation
flux --version

# Check cluster prerequisites
flux check --pre

The flux check --pre command ensures your k3s cluster meets all requirements for Flux installation.

Step 2: Bootstrap Flux with OCI Support

Bootstrap Flux v2 in your cluster with OCI artifact support enabled:

# Set your Git repository details
export GITHUB_TOKEN=your_github_token
export GITHUB_USER=your_username
export GITHUB_REPO=your-gitops-repo

# Bootstrap Flux with OCI support
flux bootstrap github \
  --owner=$GITHUB_USER \
  --repository=$GITHUB_REPO \
  --branch=main \
  --path=./clusters/production \
  --components-extra=image-reflector-controller,image-automation-controller

This command creates the necessary Flux components and configures your Git repository as the source of truth for your GitOps workflow.

Step 3: Configure SOPS/age Encryption

Install and configure age for encrypting sensitive data:

# Install age
sudo apt update
sudo apt install age -y

# Generate age key pair
age-keygen -o age.agekey

# Display public key (save this for SOPS configuration)
grep 'public key:' age.agekey

Create a SOPS configuration file in your Git repository:

# .sops.yaml
creation_rules:
  - path_regex: \.secrets\.ya?ml$
    age: age1youragepublickeyhere
  - path_regex: secrets/.*\.ya?ml$
    age: age1youragepublickeyhere

Install SOPS for secret encryption:

# Install SOPS v3.8.1
wget https://github.com/mozilla/sops/releases/download/v3.8.1/sops-v3.8.1.linux.amd64
sudo mv sops-v3.8.1.linux.amd64 /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops

Step 4: Create Encrypted Secrets

Create a sample encrypted secret:

# Create a plain secret file
cat > app-secrets.yaml << EOF
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: default
data:
  database-url: cG9zdGdyZXNxbDovL3VzZXI6cGFzc0BkYi5leGFtcGxlLmNvbS9teWRi
  api-key: bXlfc3VwZXJfc2VjcmV0X2FwaV9rZXk=
EOF

# Encrypt the secret with SOPS
SOPS_AGE_KEY_FILE=age.agekey sops --encrypt --in-place app-secrets.yaml

Step 5: Configure Flux Decryption

Create a Kubernetes secret containing your age private key:

# Create age secret in flux-system namespace
cat age.agekey |
kubectl create secret generic sops-age \
--namespace=flux-system \
--from-file=age.agekey=/dev/stdin

Configure Flux to use SOPS for decryption by creating a Kustomization:

# clusters/production/apps-kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: apps
  namespace: flux-system
spec:
  interval: 10m0s
  path: ./apps
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
  decryption:
    provider: sops
    secretRef:
      name: sops-age

Step 6: Set Up OCI Artifact Source

Configure an OCI repository as a source for your applications:

# clusters/production/oci-source.yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
  name: webapp-oci
  namespace: flux-system
spec:
  interval: 5m
  url: oci://ghcr.io/yourorg/webapp-config
  ref:
    tag: latest
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: webapp
  namespace: flux-system
spec:
  interval: 10m
  sourceRef:
    kind: OCIRepository
    name: webapp-oci
  path: "./"
  prune: true

Step 7: Implement Automated Image Updates

Configure automated image updates using Flux’s image automation:

# clusters/production/image-repository.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
  name: webapp
  namespace: flux-system
spec:
  image: ghcr.io/yourorg/webapp
  interval: 5m
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: webapp
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: webapp
  policy:
    semver:
      range: '>=1.0.0'
---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: webapp
  namespace: flux-system
spec:
  interval: 30m
  sourceRef:
    kind: GitRepository
    name: flux-system
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        email: [email protected]
        name: Flux
      messageTemplate: |
        Automated image update
        
        Automation name: {{ .AutomationObject }}
        
        Files:
        {{ range $filename, $_ := .Updated.Files -}}
        - {{ $filename }}
        {{ end -}}
        
        Objects:
        {{ range $resource, $_ := .Updated.Objects -}}
        - {{ $resource.Kind }} {{ $resource.Name }}
        {{ end -}}
    push:
      branch: main
  update:
    path: "./apps"
    strategy: Setters

Step 8: Deploy Sample Application

Create a sample application deployment with image automation markers:

# apps/webapp/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: ghcr.io/yourorg/webapp:1.0.0 # {"$imagepolicy": "flux-system:webapp"}
        ports:
        - containerPort: 8080
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: database-url

Best Practices

Follow these GitOps security practices for production deployments:

  • Separate environments: Use different Git branches or repositories for staging and production
  • RBAC implementation: Configure appropriate role-based access controls for Flux service accounts
  • Monitoring setup: Implement comprehensive monitoring with tools like our observability stack guide
  • Backup strategy: Regularly backup your Git repositories and implement automated VPS backups
  • Network security: Consider using Tailscale for secure cluster access

Security Warning: Never commit unencrypted secrets to Git repositories. Always use SOPS encryption for sensitive data and rotate age keys regularly.

Conclusion

You’ve successfully implemented a production-ready GitOps pipeline with Flux v2, encrypted secrets management, and OCI artifact support on your k3s cluster. This setup provides automated deployments, secure secrets handling, and modern container registry integration.

The combination of Flux v2’s advanced features with SOPS encryption and OCI artifacts creates a robust, scalable deployment platform. Your applications will now automatically update when new images are available, while maintaining security through encrypted secrets and version-controlled infrastructure.

Ready to deploy more advanced workloads? Explore our guides on RAG stack deployment or consider upgrading to high-performance Onidel VPS in Singapore with EPYC processors for demanding Kubernetes workloads.

Share your love