Skip to content

Deploy Applications to K3s with ArgoCD GitOps

This guide explains how to add new applications to the cluster using the GitOps workflow.

All applications are managed through ArgoCD using the App of Apps pattern:

argocd/
├── applications.yaml # Root app that manages all others
├── apps/ # Your applications
│ └── docs/ # Example: docs app
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── gateway.yaml
│ └── httproute.yaml
└── infrastructure/ # Cluster infrastructure
├── argocd/
├── cert-manager/
├── envoy-gateway/
├── external-dns/
└── external-secrets/
  1. Create application directory

    Terminal window
    mkdir -p argocd/apps/my-app
  2. Create Kubernetes manifests

    At minimum, you need:

    • deployment.yaml - Your application pods
    • service.yaml - Internal service
    • httproute.yaml - External HTTP routing (optional)
  3. Add to applications.yaml

    Add a new Application resource:

    ---
    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
    name: my-app
    namespace: argocd
    finalizers:
    - resources-finalizer.argocd.argoproj.io
    spec:
    project: default
    source:
    repoURL: https://github.com/nsudhanva/k3s-oracle.git
    targetRevision: HEAD
    path: argocd/apps/my-app
    destination:
    server: https://kubernetes.default.svc
    namespace: my-app
    syncPolicy:
    automated:
    prune: true
    selfHeal: true
    syncOptions:
    - CreateNamespace=true
  4. Commit and push

    Terminal window
    git add argocd/apps/my-app argocd/applications.yaml
    git commit -m "feat: Add my-app"
    git push
  5. Verify in ArgoCD

    ArgoCD will automatically sync within ~3 minutes, or force sync:

    Terminal window
    argocd app sync my-app
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: nginx:alpine
ports:
- containerPort: 80
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 100m
memory: 128Mi
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 80
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app-route
spec:
parentRefs:
- name: public-gateway
namespace: default
hostnames:
- "my-app.k3s.sudhanva.me"
rules:
- backendRefs:
- name: my-app
port: 80

If your app uses a private container registry (like GHCR):

spec:
template:
spec:
imagePullSecrets:
- name: regcred
containers:
- name: my-app
image: ghcr.io/your-username/my-app:latest

The regcred secret is created during cluster bootstrap with GitHub credentials.

For applications that need secrets from OCI Vault, use External Secrets Operator:

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: my-app-secrets
spec:
refreshInterval: 1h
secretStoreRef:
name: oci-vault
kind: ClusterSecretStore
target:
name: my-app-secrets
data:
- secretKey: API_KEY
remoteRef:
key: my-app-api-key
Terminal window
# Via kubectl
ssh -J ubuntu@132.226.43.62 ubuntu@10.0.2.10 \
"sudo kubectl get applications -n argocd"
# Via ArgoCD CLI
argocd app list
argocd app get my-app
Terminal window
ssh -J ubuntu@132.226.43.62 ubuntu@10.0.2.10 \
"sudo kubectl get pods -n my-app"
Terminal window
ssh -J ubuntu@132.226.43.62 ubuntu@10.0.2.10 \
"sudo kubectl logs -n my-app -l app=my-app"

The cluster runs on OCI Always Free tier with limited resources. Keep resource requests minimal:

ResourceRecommended RequestLimit
CPU10m - 50m100m - 200m
Memory32Mi - 64Mi128Mi - 256Mi

Total cluster capacity: 4 OCPUs, 24 GB RAM (shared across all workloads).