Set up ArgoCD on Kubernetes with SSL certificates, RBAC user management, and high availability for production GitOps continuous deployment workflows.
Prerequisites
- Kubernetes cluster with admin access
- kubectl installed and configured
- At least 8GB RAM available
- 3+ worker nodes for HA setup
What this solves
ArgoCD provides GitOps continuous deployment for Kubernetes applications, automatically syncing your cluster state with Git repositories. This tutorial covers production-ready ArgoCD installation with SSL/TLS encryption, role-based access control (RBAC), high availability configuration, and monitoring setup.
Prerequisites
You need a running Kubernetes cluster with kubectl access and cluster-admin permissions. If you don't have a cluster yet, follow our Kubernetes installation guide.
Step-by-step installation
Install kubectl and verify cluster access
Ensure kubectl is installed and you can access your Kubernetes cluster with admin privileges.
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 --client
Verify cluster connectivity:
kubectl cluster-info
kubectl get nodes
Create ArgoCD namespace
Create a dedicated namespace for ArgoCD components to isolate them from other applications.
kubectl create namespace argocd
Install ArgoCD with high availability
Install ArgoCD using the official high availability manifest which includes multiple replicas for production use.
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/ha/install.yaml
Wait for all ArgoCD components to be ready:
kubectl wait --for=condition=available --timeout=600s deployment/argocd-applicationset-controller -n argocd
kubectl wait --for=condition=available --timeout=600s deployment/argocd-dex-server -n argocd
kubectl wait --for=condition=available --timeout=600s deployment/argocd-notifications-controller -n argocd
kubectl wait --for=condition=available --timeout=600s deployment/argocd-redis-ha-haproxy -n argocd
kubectl wait --for=condition=available --timeout=600s deployment/argocd-repo-server -n argocd
kubectl wait --for=condition=available --timeout=600s deployment/argocd-server -n argocd
Generate SSL certificates
Create SSL certificates for secure HTTPS access. We'll use a self-signed certificate for this tutorial, but use proper certificates from a CA in production.
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout argocd.key \
-out argocd.crt \
-subj "/C=US/ST=State/L=City/O=Organization/OU=IT/CN=argocd.example.com"
kubectl create secret tls argocd-server-tls \
--cert=argocd.crt \
--key=argocd.key \
-n argocd
Configure ArgoCD server with SSL
Modify the ArgoCD server deployment to use SSL certificates and disable internal TLS since we're terminating SSL at the server level.
spec:
template:
spec:
containers:
- name: argocd-server
command:
- argocd-server
- --staticassets
- /shared/app
- --repo-server
- argocd-repo-server:8081
- --dex-server
- http://argocd-dex-server:5556
- --logformat
- text
- --loglevel
- info
- --redis
- argocd-redis-ha-haproxy:6379
- --insecure=false
- --rootpath
- /
volumeMounts:
- name: tls-certs
mountPath: /app/config/tls
readOnly: true
volumes:
- name: tls-certs
secret:
secretName: argocd-server-tls
kubectl patch deployment argocd-server -n argocd --patch-file /tmp/argocd-server-patch.yaml
Create LoadBalancer service
Expose ArgoCD server through a LoadBalancer service for external access with SSL termination.
apiVersion: v1
kind: Service
metadata:
name: argocd-server-loadbalancer
namespace: argocd
labels:
app.kubernetes.io/component: server
app.kubernetes.io/name: argocd-server
app.kubernetes.io/part-of: argocd
spec:
type: LoadBalancer
ports:
- name: https
port: 443
protocol: TCP
targetPort: 8080
- name: grpc
port: 443
protocol: TCP
targetPort: 8080
selector:
app.kubernetes.io/name: argocd-server
kubectl apply -f /tmp/argocd-server-service.yaml
Retrieve initial admin password
Get the auto-generated admin password for initial login. This password is stored in a Kubernetes secret.
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d && echo
Store this password securely as you'll need it for the initial login.
Install ArgoCD CLI
Install the ArgoCD command-line interface for managing applications and configurations.
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 755 argocd-linux-amd64 /usr/local/bin/argocd
rm argocd-linux-amd64
Configure RBAC policies
Create RBAC configuration to define user roles and permissions. This ConfigMap defines who can access what in ArgoCD.
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
namespace: argocd
labels:
app.kubernetes.io/name: argocd-rbac-cm
app.kubernetes.io/part-of: argocd
data:
policy.default: role:readonly
policy.csv: |
# Admin role - full access
p, role:admin, applications, , /*, allow
p, role:admin, clusters, , , allow
p, role:admin, repositories, , , allow
p, role:admin, certificates, , , allow
p, role:admin, accounts, , , allow
p, role:admin, gpgkeys, , , allow
# Developer role - can manage applications
p, role:developer, applications, get, /, allow
p, role:developer, applications, create, /, allow
p, role:developer, applications, update, /, allow
p, role:developer, applications, delete, /, allow
p, role:developer, applications, sync, /, allow
p, role:developer, repositories, get, *, allow
p, role:developer, clusters, get, *, allow
# Viewer role - read-only access
p, role:viewer, applications, get, /, allow
p, role:viewer, repositories, get, *, allow
p, role:viewer, clusters, get, *, allow
# Assign roles to users
g, developers, role:developer
g, viewers, role:viewer
scopes: '[groups]'
kubectl apply -f /tmp/argocd-rbac-cm.yaml
Create additional user accounts
Create local user accounts with different permission levels for team access management.
# Get the LoadBalancer IP
ARGOCD_SERVER=$(kubectl get svc argocd-server-loadbalancer -n argocd -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
Login as admin
ADMIN_PASSWORD=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d)
argocd login $ARGOCD_SERVER --username admin --password $ADMIN_PASSWORD --insecure
Create developer user
argocd account create developer --name "Developer User"
argocd account create viewer --name "Viewer User"
Set passwords for new accounts
argocd account update-password --account developer --new-password "DevSecure123!"
argocd account update-password --account viewer --new-password "ViewSecure123!"
Configure Git repository integration
Add a Git repository to ArgoCD for application deployments. This example uses a public repository, but you can configure private repositories with SSH keys or tokens.
# Add a sample Git repository
argocd repo add https://github.com/argoproj/argocd-example-apps.git
List repositories to verify
argocd repo list
For private repositories, use SSH key authentication:
# Generate SSH key for Git access
ssh-keygen -t rsa -b 4096 -f ~/.ssh/argocd_rsa -N ""
Add repository with SSH key
argocd repo add git@github.com:your-org/your-private-repo.git \
--ssh-private-key-path ~/.ssh/argocd_rsa
Deploy sample application
Create and deploy a sample application to verify ArgoCD functionality and GitOps synchronization.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: guestbook
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/argoproj/argocd-example-apps.git
targetRevision: HEAD
path: guestbook
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
kubectl apply -f /tmp/sample-app.yaml
Configure monitoring and health checks
Set up resource monitoring and health checks to ensure ArgoCD components are functioning properly.
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cmd-params-cm
namespace: argocd
labels:
app.kubernetes.io/name: argocd-cmd-params-cm
app.kubernetes.io/part-of: argocd
data:
# Enable metrics
server.enable.proxy.extension: "true"
server.metrics: "true"
repo.server.metrics: "true"
controller.metrics: "true"
# Resource limits and requests
controller.resource.customizations: |
networking.k8s.io/Ingress:
health.lua: |
hs = {}
hs.status = "Healthy"
return hs
kubectl apply -f /tmp/argocd-monitoring.yaml
Set up backup configuration
Configure automated backups of ArgoCD configuration and application definitions for disaster recovery.
#!/bin/bash
BACKUP_DIR="/var/backups/argocd"
DATE=$(date +%Y%m%d_%H%M%S)
Create backup directory
mkdir -p $BACKUP_DIR
Backup ArgoCD applications
kubectl get applications -n argocd -o yaml > $BACKUP_DIR/applications_$DATE.yaml
Backup ArgoCD projects
kubectl get appprojects -n argocd -o yaml > $BACKUP_DIR/projects_$DATE.yaml
Backup ArgoCD repositories
kubectl get secrets -n argocd -l argocd.argoproj.io/secret-type=repository -o yaml > $BACKUP_DIR/repositories_$DATE.yaml
Backup RBAC configuration
kubectl get configmap argocd-rbac-cm -n argocd -o yaml > $BACKUP_DIR/rbac_$DATE.yaml
Keep only last 30 days of backups
find $BACKUP_DIR -name "*.yaml" -mtime +30 -delete
echo "ArgoCD backup completed: $DATE"
sudo mkdir -p /var/backups/argocd
sudo cp /tmp/backup-script.sh /usr/local/bin/argocd-backup.sh
sudo chmod 755 /usr/local/bin/argocd-backup.sh
Create daily cron job for backups
echo "0 2 * /usr/local/bin/argocd-backup.sh" | sudo crontab -
Verify your setup
Check that all ArgoCD components are running correctly and the service is accessible.
# Check pod status
kubectl get pods -n argocd
Check service status
kubectl get svc -n argocd
Verify LoadBalancer external IP
kubectl get svc argocd-server-loadbalancer -n argocd
Test ArgoCD CLI connectivity
argocd version
List applications
argocd app list
Check application sync status
argocd app get guestbook
Access the ArgoCD web interface at https://YOUR_LOADBALANCER_IP and login with admin credentials. You should see the guestbook application deployed and synchronized.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Pods stuck in Pending state | Insufficient cluster resources | kubectl describe pod POD_NAME -n argocd to check resource requirements |
| SSL certificate errors | Invalid certificate configuration | Recreate certificate with correct Common Name matching your domain |
| Login fails with admin password | Password retrieval issue | kubectl -n argocd delete secret argocd-initial-admin-secret to regenerate |
| Applications not syncing | Git repository access issues | Check repository credentials with argocd repo list |
| RBAC permission denied | Incorrect role assignments | Review ConfigMap argocd-rbac-cm and restart argocd-server pods |
| LoadBalancer IP pending | No LoadBalancer controller | Install MetalLB or use NodePort service type instead |
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m'
# Configuration
readonly SCRIPT_NAME=$(basename "$0")
readonly ARGOCD_NAMESPACE="argocd"
readonly ARGOCD_DOMAIN="${1:-argocd.local}"
# Print usage
usage() {
echo "Usage: $SCRIPT_NAME [domain]"
echo " domain: ArgoCD server domain (default: argocd.local)"
echo ""
echo "Example: $SCRIPT_NAME argocd.example.com"
}
# Logging functions
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Error handler
cleanup() {
if [ $? -ne 0 ]; then
log_error "Installation failed. Cleaning up..."
kubectl delete namespace "$ARGOCD_NAMESPACE" --ignore-not-found=true 2>/dev/null || true
rm -f kubectl argocd.key argocd.crt
fi
}
trap cleanup ERR
# Check prerequisites
check_prereqs() {
if [[ $EUID -eq 0 ]]; then
log_error "Do not run this script as root. Use a user with sudo privileges."
exit 1
fi
if ! command -v sudo &> /dev/null; then
log_error "sudo is required but not installed"
exit 1
fi
if ! command -v curl &> /dev/null; then
log_error "curl is required but not installed"
exit 1
fi
}
# Detect distribution
detect_distro() {
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum check-update || true"
PKG_INSTALL="yum install -y"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution. /etc/os-release not found."
exit 1
fi
}
# Install kubectl
install_kubectl() {
log_info "[1/8] Installing kubectl..."
if command -v kubectl &> /dev/null; then
log_warn "kubectl already installed, skipping..."
return 0
fi
local kubectl_version
kubectl_version=$(curl -sL https://dl.k8s.io/release/stable.txt)
curl -sLO "https://dl.k8s.io/release/$kubectl_version/bin/linux/amd64/kubectl"
chmod 755 kubectl
sudo install -o root -g root -m 755 kubectl /usr/local/bin/kubectl
rm -f kubectl
kubectl version --client >/dev/null
log_success "kubectl installed successfully"
}
# Verify cluster access
verify_cluster() {
log_info "[2/8] Verifying Kubernetes cluster access..."
if ! kubectl cluster-info &>/dev/null; then
log_error "Cannot connect to Kubernetes cluster. Check your kubeconfig."
exit 1
fi
if ! kubectl auth can-i create namespace &>/dev/null; then
log_error "Insufficient permissions. Cluster-admin access required."
exit 1
fi
local node_count
node_count=$(kubectl get nodes --no-headers | wc -l)
if [ "$node_count" -lt 3 ]; then
log_warn "Less than 3 nodes detected. High availability may not work properly."
fi
log_success "Cluster access verified"
}
# Create ArgoCD namespace
create_namespace() {
log_info "[3/8] Creating ArgoCD namespace..."
if kubectl get namespace "$ARGOCD_NAMESPACE" &>/dev/null; then
log_warn "Namespace $ARGOCD_NAMESPACE already exists"
else
kubectl create namespace "$ARGOCD_NAMESPACE"
log_success "Namespace created"
fi
}
# Install ArgoCD
install_argocd() {
log_info "[4/8] Installing ArgoCD with high availability..."
kubectl apply -n "$ARGOCD_NAMESPACE" -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/ha/install.yaml
log_info "Waiting for ArgoCD components to be ready (up to 10 minutes)..."
local deployments=(
"argocd-applicationset-controller"
"argocd-dex-server"
"argocd-notifications-controller"
"argocd-redis-ha-haproxy"
"argocd-repo-server"
"argocd-server"
)
for deployment in "${deployments[@]}"; do
kubectl wait --for=condition=available --timeout=600s "deployment/$deployment" -n "$ARGOCD_NAMESPACE"
done
log_success "ArgoCD installed successfully"
}
# Generate SSL certificates
generate_ssl_certs() {
log_info "[5/8] Generating SSL certificates..."
if ! command -v openssl &> /dev/null; then
log_info "Installing OpenSSL..."
sudo $PKG_UPDATE
sudo $PKG_INSTALL openssl
fi
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout argocd.key \
-out argocd.crt \
-subj "/C=US/ST=CA/L=San Francisco/O=ArgoCD/OU=IT/CN=$ARGOCD_DOMAIN" \
2>/dev/null
chmod 600 argocd.key
chmod 644 argocd.crt
kubectl create secret tls argocd-server-tls \
--cert=argocd.crt \
--key=argocd.key \
-n "$ARGOCD_NAMESPACE" \
--dry-run=client -o yaml | kubectl apply -f -
rm -f argocd.key argocd.crt
log_success "SSL certificates generated and stored"
}
# Configure ArgoCD server with SSL
configure_ssl() {
log_info "[6/8] Configuring ArgoCD server with SSL..."
kubectl patch configmap argocd-cmd-params-cm -n "$ARGOCD_NAMESPACE" \
--type merge \
-p '{"data":{"server.insecure":"false"}}'
kubectl patch deployment argocd-server -n "$ARGOCD_NAMESPACE" \
--type='json' \
-p='[
{
"op": "add",
"path": "/spec/template/spec/volumes/-",
"value": {
"name": "tls-certs",
"secret": {
"secretName": "argocd-server-tls"
}
}
},
{
"op": "add",
"path": "/spec/template/spec/containers/0/volumeMounts/-",
"value": {
"name": "tls-certs",
"mountPath": "/app/config/tls",
"readOnly": true
}
}
]'
kubectl rollout status deployment/argocd-server -n "$ARGOCD_NAMESPACE" --timeout=300s
log_success "SSL configuration applied"
}
# Create LoadBalancer service
create_service() {
log_info "[7/8] Creating LoadBalancer service..."
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: argocd-server-loadbalancer
namespace: $ARGOCD_NAMESPACE
labels:
app.kubernetes.io/component: server
app.kubernetes.io/name: argocd-server
spec:
type: LoadBalancer
ports:
- name: https
port: 443
protocol: TCP
targetPort: 8080
- name: grpc
port: 443
protocol: TCP
targetPort: 8080
selector:
app.kubernetes.io/name: argocd-server
EOF
log_success "LoadBalancer service created"
}
# Verify installation
verify_installation() {
log_info "[8/8] Verifying installation..."
# Check all pods are running
kubectl wait --for=condition=Ready --timeout=300s pod -l app.kubernetes.io/part-of=argocd -n "$ARGOCD_NAMESPACE"
# Get admin password
local admin_password
admin_password=$(kubectl -n "$ARGOCD_NAMESPACE" get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d)
# Get service info
local service_ip
service_ip=$(kubectl get service argocd-server-loadbalancer -n "$ARGOCD_NAMESPACE" -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "pending")
log_success "ArgoCD installation completed successfully!"
echo ""
echo -e "${GREEN}=== ArgoCD Access Information ===${NC}"
echo "URL: https://$service_ip (or https://$ARGOCD_DOMAIN if DNS configured)"
echo "Username: admin"
echo "Password: $admin_password"
echo ""
echo -e "${YELLOW}Note: If LoadBalancer IP shows 'pending', check your cluster's LoadBalancer implementation.${NC}"
echo -e "${YELLOW}For NodePort access, run: kubectl get service argocd-server -n $ARGOCD_NAMESPACE${NC}"
}
# Main execution
main() {
if [[ "${1:-}" == "-h" ]] || [[ "${1:-}" == "--help" ]]; then
usage
exit 0
fi
log_info "Starting ArgoCD installation for domain: $ARGOCD_DOMAIN"
check_prereqs
detect_distro
install_kubectl
verify_cluster
create_namespace
install_argocd
generate_ssl_certs
configure_ssl
create_service
verify_installation
}
main "$@"
Review the script before running. Execute with: bash install.sh