Deploy Backstage with Argo CD (GitOps) on Bare-Metal Kubernetes (Traefik + Wildcard HTTPS + In-Cluster PostgreSQL + Harbor)

This post shows how I manage my Kubernetes Backstage deployment using Argo CD, so Backstage is continuously reconciled from Git (GitOps). I already have Backstage running in Kubernetes behind Traefik with wildcard TLS, and I already use a local env file (kubernetes/.env.k8s) to create the backstage-env Secret (covered in my previous Backstage-to-Kubernetes post).

After this guide, the workflow becomes:

  • Update YAML in Git → push → Argo CD syncs
  • Cluster drift is detected and reverted (self-heal)
  • Removing YAML from Git removes resources in the cluster (prune)

Target URLs:

Repo used in this guide:


What you’ll build

  • One Argo CD Application that manages everything under kubernetes/ in the Backstage repo:
    • PostgreSQL (Service + StatefulSet + PVC)
    • Backstage (Deployment + Service + Ingress)
  • A GitOps-friendly way to manage app-config.k8s.yaml as a ConfigMap (no manual kubectl create configmap)
  • A simple “Backstage-only repo” layout:
    • kubernetes/ for deployable manifests
    • argocd/application.yaml for the Argo CD Application (version-controlled, no recursion)
  • A clear “day-2 ops” workflow:
    • bump image tag
    • change Backstage config
    • update the Secret (manual) + restart pods

Prerequisites

This guide assumes you already have:

  • A working bare-metal Kubernetes cluster (kubeadm + containerd is fine)
  • Traefik installed and HTTPS already enabled via a default wildcard certificate (TLSStore) for *.maksonlee.com
  • Argo CD installed and reachable at https://argocd.maksonlee.com
  • Backstage already deployable using the manifests in this folder:
~/homelab-backstage/kubernetes/
  app-config.k8s.yaml
  homelab-backstage.yaml
  postgres.yaml
  • The namespace backstage exists
  • The Secret backstage-env exists in the backstage namespace (created from kubernetes/.env.k8s)

Repo layout for GitOps

This repo is only for Backstage, so I keep the Argo CD Application manifest inside the same repo, but outside the deployed path (to avoid recursion).

Target layout:

~/homelab-backstage/
  kubernetes/
    app-config.k8s.yaml
    homelab-backstage.yaml
    postgres.yaml
    kustomization.yaml
  argocd/
    application.yaml

Why we add Kustomize

In my repo, kubernetes/app-config.k8s.yaml is a Backstage config file, not a Kubernetes object. In a manual workflow, you might generate a ConfigMap with:

  • kubectl create configmap … –from-file

For GitOps, Argo CD needs everything it applies to be derivable from Git. The simplest approach is:

  • Add a kustomization.yaml
  • Use configMapGenerator to turn app-config.k8s.yaml into a real ConfigMap
  • Let Argo CD render the Kustomize output automatically

  1. Add kustomization.yaml to kubernetes/

Create this file:

~/homelab-backstage/kubernetes/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: backstage

resources:
  - postgres.yaml
  - homelab-backstage.yaml

generatorOptions:
  disableNameSuffixHash: false

configMapGenerator:
  - name: backstage-k8s-config
    files:
      - app-config.k8s.yaml=app-config.k8s.yaml

  1. Add argocd/application.yaml

Create the folder:

cd ~/homelab-backstage
mkdir -p argocd

Create:

~/homelab-backstage/argocd/application.yaml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: homelab-backstage
  namespace: argocd
spec:
  project: default

  source:
    repoURL: https://github.com/maksonlee/homelab-backstage.git
    targetRevision: main
    path: kubernetes

  destination:
    server: https://kubernetes.default.svc
    namespace: backstage

  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - PruneLast=true
      - ApplyOutOfSyncOnly=true

  1. Commit both files together

These two files belong together:

  • kubernetes/kustomization.yaml makes the kubernetes/ path render correctly (and generates the ConfigMap)
  • argocd/application.yaml tells Argo CD to deploy that path

Commit them in one commit:

cd ~/homelab-backstage

git add kubernetes/kustomization.yaml argocd/application.yaml
git commit -m "feat(k8s): manage backstage via gitops"
git push

  1. Apply the Application once

Apply:

kubectl apply -f argocd/application.yaml

From now on, Argo CD will watch kubernetes/ and continuously reconcile.


  1. First sync and verify

If your Backstage resources already exist (from your previous manual deployment), Argo CD will “adopt” them as long as they match what’s in Git.

argocd login argocd.maksonlee.com
# If you ever have gRPC problems through an ingress/proxy:
# argocd login argocd.maksonlee.com --grpc-web

argocd app sync homelab-backstage
argocd app wait homelab-backstage --health

Or sync from the Argo CD UI:

  • Open homelab-backstage
  • Click SYNC

Watch pods:

kubectl -n backstage get pods
kubectl -n backstage logs deploy/homelab-backstage -f

Verify the site:


How to read the Argo CD tree (what “Healthy / Synced” means)

In Argo CD, a good state looks like this:

  • App Health: Healthy
  • Sync Status: Synced to a specific Git commit on your branch (example: main)
  • Last Sync: Sync OK
  • You’ll see the resource graph:
    • ConfigMap (generated by Kustomize)
    • Service / Deployment / Ingress for Backstage
    • Service / StatefulSet / PVC for PostgreSQL

Why the ConfigMap name has a hash suffix

You’ll see something like:

  • backstage-k8s-config-79752…

That’s expected. configMapGenerator adds a hash suffix so when app-config.k8s.yaml changes, Kustomize generates a new ConfigMap and the Deployment automatically references the new name (safe rollout).

Why you see multiple ReplicaSets

Under the Backstage Deployment, you’ll see multiple ReplicaSet objects like:

  • homelab-backstage-77899b6...
  • homelab-backstage-5b44cd9...

That’s normal: Kubernetes keeps old ReplicaSets for rollback history.

If you want fewer old ReplicaSets, set:

spec:
  revisionHistoryLimit: 2

in your Backstage Deployment.


Day-2 operations

Once Argo CD owns the app, most changes are “edit Git → push”.

Update the Backstage image

Edit kubernetes/homelab-backstage.yaml and change:

image: harbor.maksonlee.com/backstage/homelab-backstage:NEW_TAG

Commit and push:

cd ~/homelab-backstage
git add kubernetes/homelab-backstage.yaml
git commit -m "chore(k8s): bump backstage image to NEW_TAG"
git push

Argo CD will detect the change and sync it.

Update Backstage config (app-config.k8s.yaml)

Edit:

  • kubernetes/app-config.k8s.yaml

Commit and push:

cd ~/homelab-backstage
git add kubernetes/app-config.k8s.yaml
git commit -m "chore(k8s): update backstage k8s config"
git push

Because the ConfigMap is generated via Kustomize, the ConfigMap name changes (hash), and the Deployment reference is updated safely.

Did this guide save you time?

Support this site

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top