How to Install ArgoCD on Ubuntu 24.04 VPS: GitOps Continuous Delivery for Kubernetes
ArgoCD turns your Git repository into the single source of truth for everything running inside a Kubernetes cluster. You commit a change to a manifest, ArgoCD notices the drift, and it reconciles the cluster back to what your repository says it should be. This guide walks through installing ArgoCD on Ubuntu 24.04 on top of a lightweight K3s cluster, exposing the UI with TLS, connecting private Git repos, wiring up SSO with Dex, and deploying your first application using the App-of-Apps pattern.
Why self-host? Running ArgoCD on your own VPS keeps cluster credentials, SSH deploy keys, and deployment history entirely under your control — no SaaS middleman touches your kubeconfig or your private repos.
Table of Contents
What is ArgoCD?
ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes. It watches Git repositories containing Kubernetes manifests, Helm charts, Kustomize overlays, or Jsonnet, and continuously reconciles the live cluster state with what the repository declares. If someone manually edits a Deployment in the cluster, ArgoCD detects the drift and (if configured) reverts it. If you merge a commit that bumps an image tag, ArgoCD rolls the change out.
The core primitive is the Application custom resource — a pointer to a source (Git repo, path, target revision) and a destination (cluster + namespace). ArgoCD's application-controller reconciles each Application on a loop, running a three-way diff between the Git-declared state, the cluster's live state, and the last-known-applied state. Sync strategies range from fully manual (you click "Sync" in the UI) to fully automated with self-heal, automatic pruning of deleted resources, and optional sync hooks for pre-sync migrations or post-sync smoke tests.
Beyond plain Applications, ArgoCD ships ApplicationSet for fan-out patterns (one template, many generated Applications across clusters or matrix dimensions), AppProject for multi-tenant isolation (restrict which repos, destinations, and resource kinds a team can use), and Rollouts integration for progressive delivery with blue/green and canary strategies. The UI gives you a topology view of every resource an Application owns, real-time sync status, and a one-click diff against Git.
Typical use cases include managing platform components (ingress controllers, cert-manager, monitoring) across many clusters from a single repo, deploying microservices with per-environment overlays, running preview environments for pull requests via ApplicationSet generators, and enforcing compliance by making Git the only path to production.
Why Self-Host ArgoCD on Your VPS?
You can pay for a hosted GitOps platform, but self-hosting ArgoCD on your own VPS offers concrete wins:
- Cost predictability — A single VPS running K3s + ArgoCD handles dozens of Applications and multiple external clusters for a flat monthly fee. Hosted GitOps tiers often charge per user, per cluster, or per Application.
- Credentials stay with you — ArgoCD stores kubeconfigs, SSH deploy keys, Helm repo credentials, and SSO client secrets as Kubernetes Secrets inside your cluster. Nothing leaves your VPS.
- Private network access — Your ArgoCD instance can reach internal Git servers (self-hosted Gitea, GitLab, Forgejo) over a VPN or private network without exposing them publicly.
- No rate limits — Hosted control planes throttle API calls and sync frequency. On your own hardware, the only limit is the hardware itself.
- Full extensibility — Install any config management plugin (cmp-server), wire up Argo CD Image Updater, enable ApplicationSets, run Argo Rollouts — all without waiting for a vendor to enable a feature flag.
- Regulatory and compliance fit — For teams under GDPR, HIPAA, SOC 2, or similar regimes, keeping the CD control plane on infrastructure you control simplifies the data-flow diagram and the auditor conversation.
Recommended VPS
For a single-cluster ArgoCD install running K3s + ArgoCD + a handful of managed Applications, we recommend the CloudCore Professional plan:
- 6 vCPU cores
- 12 GB RAM
- 100 GB NVMe SSD
- Unmetered bandwidth
Browse VPS plans — plans start well below the cost of a single hosted-GitOps user seat.
Prerequisites
Before you begin, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access
- At least 4 GB of RAM (8 GB+ recommended for comfortable K3s + ArgoCD operation)
- At least 20 GB of free disk space
- A public IP and a domain name (e.g.
argocd.example.com) with an A record pointing to the VPS, if you want HTTPS access - SSH access to the VPS
- A Git repository you want ArgoCD to manage (GitHub, GitLab, Gitea, Forgejo — anything that speaks HTTPS or SSH)
Connect to your server:
ssh root@your-server-ipStep 1: Prepare the Ubuntu 24.04 Host
Update system packages and install a couple of small utilities we will need later:
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl ca-certificates apt-transport-https gnupg gitSet the hostname (optional but tidy when you have multiple clusters):
sudo hostnamectl set-hostname argocd-01Open the firewall for the ports K3s and ArgoCD need:
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 6443/tcp
sudo ufw --force enablePort 6443 is the Kubernetes API. You can restrict it to your office IP in production.
Step 2: Install K3s (Lightweight Kubernetes)
K3s is a fully conformant Kubernetes distribution packaged into a single ~60 MB binary. It is ideal for single-node ArgoCD installs because it bundles containerd, a Traefik ingress controller, local-path storage, and a service load balancer (klipper-lb) out of the box.
Install K3s:
curl -sfL https://get.k3s.io | sh -Expected output:
[INFO] Finding release for channel stable
[INFO] Using v1.30.x+k3s1 as release
[INFO] Downloading hash ...
[INFO] Downloading binary ...
[INFO] systemd: Enabling k3s unit
[INFO] systemd: Starting k3sVerify the cluster is up:
sudo k3s kubectl get nodesExpected output:
NAME STATUS ROLES AGE VERSION
argocd-01 Ready control-plane,master 30s v1.30.x+k3s1K3s writes its kubeconfig to /etc/rancher/k3s/k3s.yaml and restricts it to root. Copy it to your user and adjust permissions so kubectl (installed next) can use it:
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown "$(id -u):$(id -g)" ~/.kube/config
chmod 600 ~/.kube/configIf you want to access the cluster from your laptop, copy ~/.kube/config to your workstation and replace server: https://127.0.0.1:6443 with server: https://your-server-ip:6443.
Step 3: Install kubectl and Helm
K3s ships its own kubectl via k3s kubectl, but installing the upstream binary gives you shell completion and tab-friendly ergonomics.
Install kubectl:
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
kubectl version --clientInstall Helm:
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm versionExpected output:
version.BuildInfo{Version:"v3.15.x", GitCommit:"...", GoVersion:"go1.22.x"}Verify kubectl can reach the cluster:
kubectl get nodes -o wide
kubectl get pods -AYou should see core K3s pods in kube-system (coredns, traefik, local-path-provisioner, metrics-server) all Running.
Step 4: Install ArgoCD via Helm
The official Argo project maintains a Helm chart at argoproj/argo-helm. Add the repo:
helm repo add argo https://argoproj.github.io/argo-helm
helm repo updateCreate a dedicated namespace:
kubectl create namespace argocdCreate a values file tuned for a single-node VPS. This disables the built-in dex-server for now (we will enable SSO later), sets the controller/repo-server to sensible resource requests, and leaves the server running in insecure mode behind the ingress (the ingress terminates TLS):
cat > argocd-values.yaml <<'EOF' global: domain: argocd.example.comconfigs: params: server.insecure: true
server: extraArgs: - --insecure service: type: ClusterIP resources: requests: cpu: 100m memory: 256Mi
controller: resources: requests: cpu: 250m memory: 512Mi
repoServer: resources: requests: cpu: 100m memory: 256Mi
redis: resources: requests: cpu: 50m memory: 64Mi
dex: enabled: false
applicationSet: enabled: true
notifications: enabled: true EOF
Replace argocd.example.com with your actual domain.
Install the chart:
helm install argocd argo/argo-cd \
--namespace argocd \
--values argocd-values.yaml \
--version 7.x.xPin the chart version explicitly in production. Check the chart releases for the latest argo-cd-* tag.
Wait for all pods to become ready:
kubectl get pods -n argocd -wExpected final state:
NAME READY STATUS RESTARTS AGE
argocd-application-controller-0 1/1 Running 0 2m
argocd-applicationset-controller-xxxxxxxxxx-xxxxx 1/1 Running 0 2m
argocd-notifications-controller-xxxxxxxxxx-xxxxx 1/1 Running 0 2m
argocd-redis-xxxxxxxxxx-xxxxx 1/1 Running 0 2m
argocd-repo-server-xxxxxxxxxx-xxxxx 1/1 Running 0 2m
argocd-server-xxxxxxxxxx-xxxxx 1/1 Running 0 2mPress Ctrl+C once everything is Running.
Step 5: Initial Login and Admin Password
ArgoCD generates a random initial admin password and stores it in a Secret. Retrieve it:
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d; echoExpected output:
Abc123XYZ...randomstringPort-forward the server to your laptop (quickest way to test before we set up ingress):
kubectl port-forward svc/argocd-server -n argocd 8080:443Open https://localhost:8080 in your browser. You will see a self-signed certificate warning — accept it for now. Log in with:
- Username:
admin - Password: the value from the command above
kubectl -n argocd delete secret argocd-initial-admin-secretStep 6: Install and Log In with the argocd CLI
The argocd CLI is how you script everything ArgoCD does. Install the latest release:
ARGOCD_VERSION=$(curl -L -s https://raw.githubusercontent.com/argoproj/argo-cd/stable/VERSION)
curl -sSL -o argocd "https://github.com/argoproj/argo-cd/releases/download/v${ARGOCD_VERSION}/argocd-linux-amd64"
sudo install -m 555 argocd /usr/local/bin/argocd
rm argocd
argocd version --clientWith the port-forward still running in another terminal, log in:
argocd login localhost:8080 \
--username admin \
--password 'your-new-password' \
--insecureVerify:
argocd cluster list
argocd app listThe in-cluster Kubernetes target is registered automatically as https://kubernetes.default.svc.
Change the admin password from the CLI (alternative to the UI):
argocd account update-passwordStep 7: Expose ArgoCD with TLS via Ingress
For production, skip the port-forward and expose ArgoCD via the K3s-bundled Traefik ingress with an automatically issued Let's Encrypt certificate via cert-manager.
Install cert-manager:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
kubectl -n cert-manager wait --for=condition=Available deployment --all --timeout=180sCreate a ClusterIssuer for Let's Encrypt:
cat <<'EOF' | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: traefik
EOFCreate an Ingress for the ArgoCD server:
cat <<'EOF' | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd-server
namespace: argocd
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
ingressClassName: traefik
rules:
- host: argocd.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 80
tls:
- hosts:
- argocd.example.com
secretName: argocd-server-tls
EOFBecause we installed ArgoCD with server.insecure: true, the server speaks plain HTTP inside the cluster and Traefik terminates TLS at the edge. That is the officially supported pattern documented in the ArgoCD ingress guide.
Wait for the certificate:
kubectl -n argocd get certificate
kubectl -n argocd describe certificate argocd-server-tlsOnce READY=True, browse to https://argocd.example.com. Log in with your admin credentials. No more self-signed warnings.
Re-login the CLI against the public URL:
argocd logout localhost:8080
argocd login argocd.example.comStep 8: Connect a Private Git Repository
ArgoCD pulls manifests from Git. For a public HTTPS repo, no credentials are needed. For a private repo, register credentials using the CLI or a Secret.
HTTPS with a Personal Access Token
argocd repo add https://github.com/your-org/your-infra.git \
--username git \
--password "$GITHUB_PAT"SSH with a Deploy Key
Generate a dedicated deploy key:
ssh-keygen -t ed25519 -f ~/argocd-deploy-key -N ""Add the public key (~/argocd-deploy-key.pub) to your Git host as a read-only deploy key. Then register the private key with ArgoCD:
argocd repo add [email protected]:your-org/your-infra.git \
--ssh-private-key-path ~/argocd-deploy-keyVerify:
argocd repo listExpected output:
TYPE NAME REPO INSECURE OCI LFS CREDS STATUS MESSAGE
git [email protected]:your-org/your-infra.git false false false true SuccessfulCredentials are persisted as Secrets in the argocd namespace (label argocd.argoproj.io/secret-type: repository). You can also declare them in Git and sync them — the bootstrap pattern we use in the next step.
Step 9: Deploy Your First App (App-of-Apps Pattern)
The App-of-Apps pattern is the canonical way to bootstrap a cluster: you create one root Application that points at a directory of other Application manifests, and ArgoCD recursively creates all of them. This turns "everything on the cluster" into a Git-managed, pull-request-reviewed artifact.
In your Git repo, create this layout:
your-infra/
├── bootstrap/
│ └── root.yaml
└── apps/
├── nginx.yaml
└── monitoring.yamlbootstrap/root.yaml — the root Application:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/your-org/your-infra.git
targetRevision: main
path: apps
directory:
recurse: true
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueapps/nginx.yaml — a sample child Application deploying the ingress-nginx Helm chart:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: ingress-nginx
namespace: argocd
spec:
project: default
source:
repoURL: https://kubernetes.github.io/ingress-nginx
chart: ingress-nginx
targetRevision: 4.11.x
helm:
values: |
controller:
service:
type: ClusterIP
destination:
server: https://kubernetes.default.svc
namespace: ingress-nginx
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueCommit and push. Then, one-time, apply the root Application:
kubectl apply -f bootstrap/root.yamlArgoCD picks up the root Application, which in turn creates every Application under apps/. From this point on, adding a new app is just adding a new file under apps/ and merging a PR — no more kubectl apply on your laptop.
Watch it sync:
argocd app list
argocd app get root
argocd app sync rootExpected output:
NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS
root https://kubernetes.default.svc argocd default Synced Healthy Auto-Prune <none>
ingress-nginx https://kubernetes.default.svc ingress-nginx default Synced Healthy Auto-Prune <none>Open the UI and you will see a topology tree: the root Application on the left, the child Applications in the middle, and every Kubernetes resource (Deployment, Service, ConfigMap, Pod, etc.) on the right.
Step 10: Configure SSO with Dex and OIDC
For a team install, local accounts quickly become unmanageable. ArgoCD ships an embedded Dex server that federates to GitHub, GitLab, Google, Okta, Microsoft Entra ID, and any generic OIDC provider.
First, re-enable Dex. Update your argocd-values.yaml:
dex: enabled: true
configs: cm: url: https://argocd.example.com dex.config: | connectors: - type: github id: github name: GitHub config: clientID: $dex.github.clientID clientSecret: $dex.github.clientSecret orgs: - name: your-github-org
Create the GitHub OAuth app at https://github.com/organizations/your-github-org/settings/applications:
- Homepage URL:
https://argocd.example.com - Authorization callback URL:
https://argocd.example.com/api/dex/callback
kubectl -n argocd create secret generic argocd-secret \
--from-literal=dex.github.clientID=YOUR_CLIENT_ID \
--from-literal=dex.github.clientSecret=YOUR_CLIENT_SECRET \
--dry-run=client -o yaml | kubectl apply -f -In practice, manage this Secret with sealed-secrets or external-secrets so it can live in Git safely.
Upgrade the release:
helm upgrade argocd argo/argo-cd \
--namespace argocd \
--values argocd-values.yamlRestart the affected deployments:
kubectl -n argocd rollout restart deployment argocd-server
kubectl -n argocd rollout restart deployment argocd-dex-serverOpen https://argocd.example.com — you will now see a "LOG IN VIA GITHUB" button. Clicking it redirects to GitHub, authorizes, and drops you back into ArgoCD as an authenticated SSO user.
For Google Workspace, Okta, or Entra ID, swap the connectors block for the corresponding Dex connector config. The ArgoCD user-management docs list every supported provider.
Step 11: RBAC and Team Permissions
Out of the box, any authenticated user has read-only cluster access. To grant granular permissions, edit the RBAC ConfigMap argocd-rbac-cm.
cat <<'EOF' | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: argocd-rbac-cm namespace: argocd data: policy.default: role:readonly scopes: "[groups, email]" policy.csv: | # Built-in admin role g, your-github-org:platform-admins, role:admin
# App developers: sync their own project only p, role:dev, applications, get, dev/*, allow p, role:dev, applications, sync, dev/*, allow p, role:dev, applications, action/, dev/, allow p, role:dev, logs, get, dev/*, allow g, your-github-org:developers, role:dev EOF
Key points:
- The first column of
policy.csvisp(policy) org(group-to-role binding). - Policies take the form
p, <subject>, <resource>, <action>, <object>, <effect>. - The
<object>for applications is<project>/<app>, sodev/*restricts the developer role to thedevAppProject. policy.default: role:readonlyis the fallback for any authenticated user without an explicit binding.
dev AppProject so the developer role has something to sync:apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: dev
namespace: argocd
spec:
description: Developer-managed applications
sourceRepos:
- https://github.com/your-org/dev-apps.git
destinations:
- namespace: dev-*
server: https://kubernetes.default.svc
clusterResourceWhitelist: []
namespaceResourceWhitelist:
- group: "*"
kind: "*"Commit this manifest to your infra repo and let the App-of-Apps pattern apply it. Now developers in the developers GitHub team can sync any Application under project: dev into any dev-* namespace, but cannot touch platform Applications.
Full syntax is documented at argo-cd.readthedocs.io/en/stable/operator-manual/rbac/.
Step 12: Automate Image Updates with Argo CD Image Updater
ArgoCD reconciles Git → cluster. It does not, by itself, bump image tags when you push a new container build. Argo CD Image Updater closes that loop: it polls your container registry for new tags and either writes them back to Git (best practice) or patches the Application directly.
Install the Helm chart:
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd-image-updater argo/argocd-image-updater \
--namespace argocd \
--set config.argocd.serverAddress=argocd-server.argocd.svc.cluster.local \
--set config.argocd.plaintext=trueAnnotate an Application to opt it in to image updates (write-back to Git):
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-api
namespace: argocd
annotations:
argocd-image-updater.argoproj.io/image-list: api=ghcr.io/your-org/my-api
argocd-image-updater.argoproj.io/api.update-strategy: semver
argocd-image-updater.argoproj.io/api.allow-tags: regexp:^v[0-9]+\.[0-9]+\.[0-9]+$
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/git-branch: main
spec:
project: default
source:
repoURL: https://github.com/your-org/your-infra.git
targetRevision: main
path: apps/my-api
destination:
server: https://kubernetes.default.svc
namespace: prodNow when you push ghcr.io/your-org/my-api:v1.4.2, the image updater:
image: ghcr.io/your-org/my-api:v1.4.2 into a .argocd-source-my-api.yaml override file in your repo.You still get the Git audit trail (every image bump is a commit), but you no longer need a CI job to bump tags. The full annotation reference is in the Argo CD Image Updater documentation.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
argocd-server pod CrashLoopBackOff | TLS config mismatch when running behind an ingress that also terminates TLS | Ensure server.insecure: true in values AND --insecure in server.extraArgs; restart: kubectl -n argocd rollout restart deploy argocd-server |
Unable to load data: connection refused in UI | argocd-repo-server not reachable | Check pod: kubectl -n argocd get pod -l app.kubernetes.io/name=argocd-repo-server; check logs for credentials errors |
Ingress returns 404 page not found | Wrong ingressClassName or ArgoCD Service in a different namespace | Verify kubectl get ingressclass matches the one in your Ingress; ensure backend service is argocd-server in namespace argocd |
repository not accessible on repo add | Wrong credentials or private repo without creds | Re-run argocd repo add with --username/--password or --ssh-private-key-path; for self-signed Git hosts add --insecure-skip-server-verification |
ComparisonError: repository not found | Typo in repoURL or repo not yet connected | List with argocd repo list; ensure repoURL in Application matches exactly (trailing .git, case) |
App stuck in OutOfSync despite auto-sync | Sync policy not set or a resource is ignored | Check spec.syncPolicy.automated; inspect argocd app diff <app> for what differs |
certificate is not yet ready on ingress | cert-manager HTTP-01 challenge failing | Verify DNS A record resolves to the VPS; check kubectl describe challenge -A; ensure port 80 is open |
| SSO login loops back to ArgoCD | Dex url or callback URL mismatch | Ensure configs.cm.url matches the public URL AND the OAuth app callback is <url>/api/dex/callback |
image updater not bumping tags | Missing Git write-back credentials | Register a Secret with git credentials and reference it via argocd-image-updater.argoproj.io/write-back-target: helmvalues annotations; see Image Updater docs |
application controller high memory | Too many Applications on a small VPS | Bump controller.resources.requests.memory and upgrade VPS; consider sharding with ARGOCD_CONTROLLER_REPLICAS |
Reading the logs
The three most useful log streams:
kubectl -n argocd logs -f deploy/argocd-server
kubectl -n argocd logs -f deploy/argocd-repo-server
kubectl -n argocd logs -f statefulset/argocd-application-controllerFor sync-specific debugging:
argocd app get <app-name> --show-operation
argocd app history <app-name>
argocd app manifests <app-name>FAQ
Do I need a Kubernetes cluster to run ArgoCD?
Yes — ArgoCD itself runs as a set of Kubernetes controllers. It is not a standalone binary. For a single-VPS setup, K3s is the lightest path (installs in 30 seconds, uses ~500 MB RAM at idle). If you already run Docker, you can also use k3d or kind, but for a production VPS, native K3s is preferable because it survives reboots and integrates with systemd. See our K3s install guide for a full walkthrough.
Can ArgoCD manage clusters other than the one it runs on?
Yes — this is one of its killer features. You register an external cluster once with argocd cluster add <context-name>, which creates a Secret containing the kubeconfig. From then on, any Application can target that cluster via spec.destination.server: https://api.external-cluster.example.com. A single ArgoCD control plane on a small VPS can manage dozens of downstream production clusters. Make sure the control plane VPS can reach each cluster's API server (via public IP, VPN, or a tunnel).
How is ArgoCD different from Flux?
Both are CNCF graduated GitOps projects and both pull from Git. The main practical differences: ArgoCD has a polished web UI and is application-centric (the Application CRD is the unit of deployment); Flux is GitOps-source-centric (you declare GitRepository and Kustomization resources with no built-in UI, though Weave GitOps provides one). ArgoCD's ApplicationSet is more flexible for multi-cluster fan-out; Flux's Kustomize controller handles Kustomize natively without needing a plugin. Teams that want a UI-driven, application-first experience tend to pick ArgoCD. Teams doing heavy Kustomize multi-tenancy with minimal UI tend to pick Flux. Both are production-grade.
How do I back up ArgoCD state?
ArgoCD is largely stateless — its desired state lives in Git. The things you should back up are the argocd namespace Secrets (repository credentials, cluster credentials, SSH keys, OIDC client secrets) and any ConfigMaps you have customized (argocd-cm, argocd-rbac-cm, argocd-notifications-cm). Export them with kubectl -n argocd get secret,configmap -o yaml > argocd-backup.yaml. Better: manage them as sealed-secrets or external-secrets in Git so recovery is just re-applying the bootstrap. For disaster recovery at the cluster level, take a snapshot of the VPS volume or use K3s etcd snapshots (k3s etcd-snapshot save).
Is it safe to expose ArgoCD to the public internet?
With HTTPS (via cert-manager), SSO enabled (Dex or OIDC), the initial admin account disabled, and proper RBAC, yes — it is a reasonable production posture and is what most teams do. Additional hardening: put ArgoCD behind a WAF or Cloudflare, disable the local admin account entirely (accounts.admin.enabled: false), enforce MFA at the IdP level, and restrict source IPs on the Ingress for the /api path. For extra paranoia, keep ArgoCD on a private VPN and expose only through Tailscale or a Cloudflare Tunnel. See How to Install Nginx on Ubuntu if you prefer terminating TLS in front of K3s instead of at the Traefik ingress.
What resources does a small ArgoCD install actually use?
On an idle single-node cluster managing ~10 Applications: roughly 1.5 GB RAM and 0.3 CPU cores total across all ArgoCD components. The application-controller is the heaviest — it holds the reconciliation cache in memory and scales with the number of managed resources. Expect +100-200 MB per 100 additional Applications. The 6 vCPU / 12 GB CloudCore Professional plan handles a few hundred Applications comfortably.
Can I run ArgoCD with Docker Compose instead of Kubernetes?
No supported path. ArgoCD is fundamentally a Kubernetes controller — the entire reconciliation model assumes a Kubernetes API to watch and patch. If you want GitOps-style delivery for pure Docker workloads, look at Portainer's Git integration, Komodo, or Coolify. If you already use Docker for other things, installing K3s alongside it is straightforward — see How to Install Docker on Ubuntu.
How do I upgrade ArgoCD?
Because you installed via Helm, upgrades are a single command:
helm repo update
helm upgrade argocd argo/argo-cd \
--namespace argocd \
--values argocd-values.yaml \
--version <new-chart-version>Always read the release notes between your current version and the target — ArgoCD occasionally changes CRDs across minor versions, which require a one-time kubectl apply -f of the updated CRD manifests before the Helm upgrade. Test in a staging cluster first.
Next Steps
- Deploy Argo Rollouts for progressive delivery — Layer Argo Rollouts on top of ArgoCD to run blue/green and canary deployments driven by Prometheus metrics.
- Add notifications — Wire up the bundled
argocd-notificationscontroller to send Slack, Telegram, or email alerts on sync failures, health degradations, and new deployments. - Move to ApplicationSet for multi-env fan-out — Replace manual per-environment Application manifests with an ApplicationSet that generates one Application per directory, per Git branch, or per cluster.
- Integrate secret management — Install External Secrets Operator or sealed-secrets so every Secret in your infra repo is safely versioned in Git.
- Harden the cluster — Enable network policies, pod security admission, and audit logging on K3s. See our K3s install guide for the production checklist.
Need more horsepower? The CloudCore Professional plan gives you 6 vCPU, 12 GB RAM, and 100 GB NVMe — enough to run K3s, ArgoCD, and a realistic fleet of managed Applications on a single VPS. Browse plans.