Deploying and Managing ArgoCD Using Terraform on Kubernetes
ArgoCD is a powerful GitOps continuous delivery tool for Kubernetes that enables declarative management of application deployments. By using Terraform, we can automate the deployment and management of ArgoCD, ensuring an infrastructure-as-code (IaC) approach for Kubernetes GitOps workflows.This guide will cover the step-by-step process of deploying ArgoCD using Terraform on a Kubernetes cluster, along with best practices for managing its configuration and lifecycle.
Video Tutorial
Prerequisites
- Terraform (v1.0+)
- Kubernetes cluster
- kubectl configured
- Helm provider (optional)
- Git repository access
- Basic understanding of Terraform and ArgoCD
Project Structure
argocd-terraform
├── README.md
├── environments
│ ├── dev
│ │ ├── argocd-apps
│ │ │ ├── README.md
│ │ │ ├── backend.tf
│ │ │ ├── main.tf
│ │ │ ├── outputs.tf
│ │ │ ├── providers.tf
│ │ │ ├── terraform.tfvars
│ │ │ └── variables.tf
│ │ └── argocd-server
│ │ ├── README.md
│ │ ├── backend.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── providers.tf
│ │ ├── terraform.tfvars
│ │ └── variables.tf
│ ├── production
│ └── staging
└── modules
├── applications
│ ├── README.md
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
└── argocd
├── README.md
├── main.tf
├── outputs.tf
└── variables.tf
Step 1: Initialize Terraform and Configure Kubernetes Provider
providers.tf
terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.23.0"
}
helm = {
source = "hashicorp/helm"
version = "~> 2.11.0"
}
null = {
source = "hashicorp/null"
version = "~> 3.2.0"
}
}
}
provider "kubernetes" {
config_path = var.kubeconfig_path
}
provider "helm" {
kubernetes {
config_path = var.kubeconfig_path
}
}
versions.tf
terraform {
required_version = ">= 1.0.0"
}
Step 2: ArgoCD Server Module
modules/argocd/main.tf
resource "kubernetes_namespace" "argocd" {
metadata {
name = var.namespace
labels = {
"app.kubernetes.io/managed-by" = "terraform"
}
}
}
resource "helm_release" "argocd" {
name = "argocd"
repository = "https://argoproj.github.io/argo-helm"
chart = "argo-cd"
version = var.argocd_version
namespace = kubernetes_namespace.argocd.metadata[0].name
values = [
yamlencode({
server = {
extraArgs = ["--insecure"]
service = {
type = var.service_type
}
ingress = {
enabled = var.ingress_enabled
hosts = var.ingress_hosts
tls = var.ingress_tls
}
}
dex = {
enabled = var.dex_enabled
}
notifications = {
enabled = var.notifications_enabled
}
ha = {
enabled = var.ha_enabled
}
})
]
}
modules/argocd/variables.tf
variable "namespace" {
description = "Namespace for ArgoCD installation"
type = string
default = "argocd"
}
variable "argocd_version" {
description = "Version of ArgoCD Helm chart"
type = string
default = "5.46.7"
}
variable "service_type" {
description = "Service type for ArgoCD server"
type = string
default = "ClusterIP"
}
variable "ingress_enabled" {
description = "Enable ingress for ArgoCD server"
type = bool
default = false
}
variable "ingress_hosts" {
description = "Ingress hosts for ArgoCD server"
type = list(string)
default = []
}
variable "ingress_tls" {
description = "TLS configuration for ingress"
type = list(map(string))
default = []
}
variable "dex_enabled" {
description = "Enable Dex for SSO"
type = bool
default = false
}
variable "notifications_enabled" {
description = "Enable notifications"
type = bool
default = false
}
variable "ha_enabled" {
description = "Enable high availability"
type = bool
default = false
}
variable "admin_password" {
description = "Admin password for ArgoCD"
type = string
sensitive = true
}
variable "server_secretkey" {
description = "Server secret key"
type = string
sensitive = true
}
Step 3: Applications Module
modules/applications/main.tf
resource "kubernetes_manifest" "application" {
for_each = var.applications
manifest = {
apiVersion = "argoproj.io/v1alpha1"
kind = "Application"
metadata = {
name = each.key
namespace = var.argocd_namespace
}
spec = {
project = each.value.project
source = {
repoURL = each.value.repo_url
targetRevision = each.value.target_revision
path = each.value.path
}
destination = {
server = each.value.destination_server
namespace = each.value.destination_namespace
}
syncPolicy = {
automated = {
prune = each.value.sync_prune
selfHeal = each.value.sync_self_heal
}
syncOptions = [
"CreateNamespace=true"
]
}
}
}
}
modules/applications/variables.tf
variable "argocd_namespace" {
description = "ArgoCD namespace"
type = string
}
variable "applications" {
description = "Map of applications to create"
type = map(object({
project = string
repo_url = string
target_revision = string
path = string
destination_server = string
destination_namespace = string
sync_prune = bool
sync_self_heal = bool
}))
}
variable "helm_release_name" {
description = "Name of the ArgoCD Helm release to depend on"
type = string
}
Step 4: Deployments Cluster ArgoCD Server Configuration
main.tf
module "argocd" {
source = "../modules/argocd"
namespace = var.argocd_namespace
argocd_version = var.argocd_version
service_type = var.service_type
ingress_enabled = var.ingress_enabled
ingress_hosts = var.ingress_hosts
ingress_tls = var.ingress_tls
dex_enabled = var.dex_enabled
notifications_enabled = var.notifications_enabled
ha_enabled = var.ha_enabled
admin_password = var.admin_password
server_secretkey = var.server_secretkey
}
variables.tf
variable "kubeconfig_path" {
description = "Path to kubeconfig file"
type = string
}
variable "argocd_namespace" {
description = "Namespace for ArgoCD installation"
type = string
default = "argocd"
}
variable "argocd_version" {
description = "Version of ArgoCD Helm chart"
type = string
default = "5.46.7"
}
variable "service_type" {
description = "Service type for ArgoCD server"
type = string
default = "ClusterIP"
}
variable "ingress_enabled" {
description = "Enable ingress for ArgoCD server"
type = bool
default = false
}
variable "ingress_hosts" {
description = "Ingress hosts for ArgoCD server"
type = list(string)
default = []
}
variable "ingress_tls" {
description = "TLS configuration for ingress"
type = list(map(string))
default = []
}
variable "applications" {
description = "Map of applications to create"
type = map(object({
project = string
repo_url = string
target_revision = string
path = string
destination_server = string
destination_namespace = string
sync_prune = bool
sync_self_heal = bool
}))
default = {}
}
variable "dex_enabled" {
description = "Enable Dex authentication"
type = bool
default = false
}
variable "notifications_enabled" {
description = "Enable notifications"
type = bool
default = false
}
variable "ha_enabled" {
description = "Enable HA mode"
type = bool
default = false
}
variable "admin_password" {
description = "Admin password for ArgoCD"
type = string
default = "password"
}
variable "server_secretkey" {
description = "Secret key for ArgoCD server"
type = string
default = "secretkey"
}
outputs.tf
output "argocd_namespace" {
description = "Namespace where ArgoCD is installed"
value = module.argocd.namespace
}
output "argocd_server_service" {
description = "ArgoCD server service details"
value = module.argocd.server_service
sensitive = true
}
output "helm_release_name" {
description = "Name of the ArgoCD Helm release"
value = module.argocd.helm_release_name
}
backend.tf
terraform {
backend "s3" {
bucket = "your-s3-bucket-name"
key = "terraform/state/dev/argocd-server/terraform.tfstate"
region = "your-region"
}
}
Usage Example
terraform.tfvars
kubeconfig_path = "~/.kube/config"
argocd_namespace = "argocd"
service_type = "LoadBalancer"
ingress_enabled = true
ingress_hosts = ["argocd.example.com"]
dex_enabled = true
notifications_enabled = true
ha_enabled = true
admin_password = "password"
server_secretkey = "secretkey"
Step 5: Development Cluster ArgoCD Applications Configuration
main.tf
resource "null_resource" "wait_for_crds" {
depends_on = [var.helm_release_name]
provisioner "local-exec" {
command = <<EOF
while ! kubectl get crd applications.argoproj.io &>/dev/null; do
echo "Waiting for ArgoCD CRDs to be installed..."
sleep 10
done
EOF
}
}
module "applications" {
source = "../modules/applications"
depends_on = [null_resource.wait_for_crds]
argocd_namespace = var.argocd_namespace
helm_release_name = var.helm_release_name
applications = var.applications
}
variables.tf
variable "kubeconfig_path" {
description = "Path to kubeconfig file"
type = string
}
variable "argocd_namespace" {
description = "Namespace for ArgoCD installation"
type = string
default = "argocd"
}
variable "argocd_version" {
description = "Version of ArgoCD Helm chart"
type = string
default = "5.46.7"
}
variable "helm_release_name" {
description = "Name of the ArgoCD Helm release to depend on"
type = string
}
variable "applications" {
description = "Map of applications to create"
type = map(object({
project = string
repo_url = string
target_revision = string
path = string
destination_server = string
destination_namespace = string
sync_prune = bool
sync_self_heal = bool
}))
default = {}
}
outputs.tf
output "applications" {
description = "Created applications"
value = module.applications.applications
}
backend.tf
terraform {
backend "s3" {
bucket = "your-s3-bucket-name"
key = "terraform/state/dev/argocd-apps/terraform.tfstate"
region = "your-region"
}
}
Usage Example
terraform.tfvars
kubeconfig_path = "~/.kube/config"
argocd_namespace = "argocd"
helm_release_name = "argocd"
applications = {
"guestbook" = {
project = "default"
repo_url = "https://github.com/argoproj/argocd-example-apps.git"
target_revision = "HEAD"
path = "guestbook"
destination_server = "https://kubernetes.default.svc"
destination_namespace = "guestbook"
sync_prune = true
sync_self_heal = true
}
}
Step 6: Access ArgoCD UI
6.1 Get Initial Admin Password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
6.2 Access the UI
Option 1: Port Forwarding
kubectl port-forward svc/argocd-server -n argocd 8080:443
Best Practices
-
Secret Management
- Use Vault or AWS Secrets Manager for sensitive values
- Never commit secrets to version control
- Utilize Terraform’s sensitive attribute
-
State Management
- Use remote state storage (S3, GCS, Azure Storage)
- Enable state locking
- Implement state encryption
-
Module Organization
- Keep modules focused and single-purpose
- Use consistent naming conventions
- Document all variables and outputs
-
Security
- Enable RBAC
- Configure network policies
- Use TLS for ingress
- Implement least privilege access
-
High Availability
- Enable HA mode for production
- Configure proper resource requests/limits
- Use node affinity and anti-affinity rules
Troubleshooting
Common Issues
-
Helm Provider Issues
# Verify Helm repository helm repo add argo https://argoproj.github.io/argo-helm helm repo update
-
Application Sync Issues
# Check application status kubectl get applications -n argocd kubectl describe application <name> -n argocd
-
Authentication Issues
# Verify secrets kubectl get secrets -n argocd kubectl describe secret argocd-secret -n argocd
Maintenance
Upgrading ArgoCD
- Update the
argocd_version
variable - Run Terraform plan and apply
- Verify the upgrade:
kubectl get pods -n argocd argocd version
Backup and Restore
-
Backup State
terraform show -json > terraform_state_backup.json
-
Backup ArgoCD
kubectl get applications -n argocd -o yaml > applications_backup.yaml kubectl get appprojects -n argocd -o yaml > projects_backup.yaml