Learning GitOps - FluxCD - Your First GitOps Deployment
Episode 6 of 36

Learning GitOps - FluxCD - Your First GitOps Deployment

Your first GitOps deployment: manifest repository structure, creating the GitRepository source and Kustomization, deploying your first application, then testing auto-sync by changing the configuration through Git.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 5 you mastered the Flux CLI — flux check, flux get, flux reconcile, up to flux create. Now we put all those tools to real use: your first GitOps deployment. Starting with this episode, Git becomes the only source of truth; the cluster merely follows what's in the repository.

We'll set up the repository structure, the GitRepository source, and the Kustomization, then deploy the first application and test the GitOps loop.

Manifest Repository Structure

The manifest repository (fleet repo) is where all cluster configuration is stored:

Manifest repository structure
gitops-cluster/
├── clusters/production/
   └── flux-system/
├── apps/webapp/
   ├── deployment.yaml
   └── service.yaml
└── infrastructure/
    └── ingress-nginx/
  • clusters/ — cluster-specific configuration, including the bootstrap files from flux bootstrap in episode 4.
  • apps/ — business application configuration: one folder per application.
  • infrastructure/ — platform components like the ingress controller, monitoring, or storage.

Tip

There are no rigid rules; what matters is consistency. Some teams put everything under apps/, others separate infrastructure/ because its PR permissions differ. Choose what fits your team's size.

Creating the GitRepository Source

The source is the starting point of every GitOps flow. Flux doesn't apply manifests directly from the repo; it copies the repo into a local cache, then renders and applies those manifests. The object that manages this is GitRepository:

gitrepository.yaml in flux-system
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: webapp
  namespace: flux-system
spec:
  interval: 1m
  ref:
    branch: main
  url: https://github.com/arman/webapp-manifests.git
  • interval — how often Flux checks the repo. For production, usually 5m to 10m; for a lab, 1m so changes are detected quickly.
  • ref — can be branch, tag, commit, or semver. Details are covered in episode 9.

SSH vs HTTPS

Authentication is configured via spec.secretRef:

MethodURLSecretBest for
HTTPS + PAThttps://github.com/...username + passwordGitHub, GitLab
HTTPS + tokenhttps://github.com/...tokenToken without a username
SSHgit@github.com:...identity + known_hostsPrivate repos, strict security

Public repos don't need a secret at all. Private HTTPS repos use basic auth with a personal access token as the password:

Create a secret for a private repo
flux create secret git webapp-auth \
  --url=https://github.com/arman/webapp-manifests.git \
  --username=arman \
  --password=$GITHUB_TOKEN

Important

Never put a token in a manifest. Always store it in a Kubernetes Secret, and never commit that Secret to Git. For production needs, episode 11 covers SOPS and the External Secrets Operator.

Verify the source syncs successfully:

Check source status
flux get sources git

The flux get sources git output shows the READY and STATUS columns. If READY is True, the repo has been successfully copied into the Flux cache.

Creating a Kustomization

GitRepository only provides the contents of the repo. The Kustomization is what determines what gets applied — this is a Kustomize Controller CRD, not to be confused with the kustomization.yaml file covered in episode 7:

Kustomization for webapp
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: webapp
  namespace: flux-system
spec:
  interval: 5m
  path: ./webapp
  prune: true
  sourceRef:
    kind: GitRepository
    name: webapp
  targetNamespace: webapp
  • sourceRef — points to which source to use, here the GitRepository named webapp.
  • path — the directory inside the repo to render. It can be a folder containing a kustomization.yaml, or plain YAML files directly.
  • targetNamespace — the default namespace for resources that don't specify one.
  • prune — removes resources that are no longer in Git (garbage collection).

Deploying Your First Application

Now let's populate the repo with the webapp application manifests in the webapp/ directory:

webapp/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
  namespace: webapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
        - name: webapp
          image: nginx:1.27
          ports:
            - containerPort: 80

Add a namespace.yaml file to create the webapp namespace, then commit everything:

Commit manifests and trigger sync
git add webapp/
git commit -m "feat: add webapp manifests"
git push origin main
flux reconcile source git webapp

flux reconcile source git webapp forces Flux to fetch the latest version without waiting for the interval.

Verifying the Deployment

Wait a few seconds, then check:

Check the reconciliation result
flux get kustomizations
kubectl get deployment webapp -n webapp
kubectl get pods -n webapp

Check the latest events if there are any problems:

View resource events
kubectl describe kustomization webapp -n flux-system
kubectl get events -n flux-system --sort-by=.lastTimestamp | tail -20

Warning

If flux get kustomizations shows READY as False, the message in the STATUS column is your best clue. The most common causes: a wrong path, the namespace not created yet, or sourceRef not yet Ready. Also check kubectl describe and events before panicking.

Changing Configuration Through Git

This is where the heart of GitOps is tested. Change the replica count from 2 to 4, then commit and push — without touching kubectl apply:

Change configuration and push
git add webapp/deployment.yaml
git commit -m "chore: scale webapp to 4 replicas"
git push origin main
flux reconcile kustomization webapp
kubectl get pods -n webapp

Flux detects the change in the repo, re-renders the manifests, and adjusts the cluster. No manual apply, no drift — Git and the cluster stay aligned.

Note

Compare this with the old way: log into the cluster, change the deployment manually, forget to record the change. With GitOps, every change has an audit trail in Git and can be rolled back with just a git revert followed by a push.

Closing

Your first GitOps deployment is running and tested:

  • The clusters/, apps/, infrastructure/ structure keeps the manifest repo tidy and easy to navigate.
  • GitRepository provides the repo contents to Flux; authentication via HTTPS or SSH is handled through Secrets.
  • Kustomization determines the path, source, target namespace, and prune behavior.
  • Commit to Git, push, then Flux adjusts the cluster — the GitOps loop works end to end.
  • Verify yourself with flux get, kubectl get, and events before trusting the status.

Everything still uses plain manifests. In episode 7 we'll work with Kustomize: reusable base templates, overlays per environment, patch strategies, and Kustomization CRD features that the standalone Kustomize binary doesn't have. See you!

Learning GitOps - FluxCD - Your First GitOps Deployment | Learn FluxCD & GitOps