DEV Community

Python-T Point
Python-T Point

Posted on • Originally published at pythontpoint.in

🚀 Setting up an Argo CD GitOps pipeline with Dockerized Python microservices

🚀 Argo CD GitOps pipeline with Dockerized Python microservices — You can set up an Argo CD GitOps pipeline with Dockerized Python microservices without a separate CI server — Argo CD can drive image builds directly from the Git repository.

Argo CD GitOps pipeline with Dockerized Python microservices

Argo CD watches the repository, triggers a Docker build via a custom tool, and syncs the resulting image tag into the Kubernetes Deployment. This works because the pipeline eliminates the hand‑off between a CI system and a CD system, removing extra Git checkout and image‑push steps and thus cutting latency from minutes to seconds while keeping the source of truth in a single Git repo.

📑 Table of Contents

  • 🚀 Argo CD GitOps pipeline with Dockerized Python microservices — You can set up an Argo CD GitOps pipeline with Dockerized Python microservices without a separate CI server — Argo CD can drive image builds directly from the Git repository.
  • 📦 Dockerizing Python Microservice — Why It Matters
  • 🐍 Base Image Choice — Alpine vs Slim
  • 📦 Multi‑Stage Build — Reducing Attack Surface
  • 🚀 Argo CD Fundamentals — What It Does
  • 🔑 Repository Access — SSH vs HTTPS
  • 📋 Sync Policy — Automated vs Manual
  • 🛠 Kubernetes Manifests for Python Service — How They Fit
  • 📈 Health Probes — Reducing False Restarts
  • 🏷 Image Tag Strategy — Immutable Tags
  • 🔧 GitOps Workflow — Wiring Everything Together
  • 📊 Comparison — Argo CD Application vs Helm‑only Approach
  • 🟩 Final Thoughts
  • ❓ Frequently Asked Questions
  • How does Argo CD detect changes in the Docker image?
  • Can I use a private container registry with this pipeline?
  • What happens if a deployment fails health checks?
  • 📚 References & Further Reading

📦 Dockerizing Python Microservice — Why It Matters

A Dockerized Python microservice creates an immutable runtime that can be deployed repeatedly across any Kubernetes node, guaranteeing that the same binary runs locally and in production and eliminating environment drift.

# Dockerfile
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install -no-cache-dir -r requirements.txt FROM python:3.11-slim
WORKDIR /app
COPY -from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY . .
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Enter fullscreen mode Exit fullscreen mode

What this does: (More onPythonTPoint tutorials)

  • builder stage: installs dependencies in an isolated layer, allowing the final image to avoid the build‑time tools.
  • final stage: copies only the compiled packages and source code, producing a small image (~80 MB) that starts quickly.
  • CMD: launches a uvicorn server, the typical ASGI entry point for FastAPI or Starlette applications.

🐍 Base Image Choice — Alpine vs Slim

Choosing python:3.11-slim over alpine avoids compatibility issues with wheels that require compiled C extensions, because slim uses Debian’s glibc while Alpine relies on musl libc. The slim variant still keeps the image size low, typically under 100 MB.

📦 Multi‑Stage Build — Reducing Attack Surface

By discarding the build environment, the final image contains only runtime dependencies. The builder stage includes compilers and build‑time packages; after copying only site‑packages, the final image lacks gcc, make, and related binaries, reducing the number of exploitable components from dozens to a handful.

Key point: Docker multi‑stage builds give you a reproducible environment and a minimal attack surface, both essential for a secure GitOps pipeline.


🚀 Argo CD Fundamentals — What It Does

Argo CD continuously reconciles the live cluster state to match the desired state stored in a Git repository. It runs a watch loop that pulls the repo every 30 seconds, computes a diff against the live resources, and applies changes via the Kubernetes API, guaranteeing eventual consistency.

# application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata: name: python-microservice
spec: project: default source: repoURL: https://github.com/example/python-microservice.git targetRevision: HEAD path: k8s destination: server: https://kubernetes.default.svc namespace: production syncPolicy: automated: prune: true selfHeal: true
Enter fullscreen mode Exit fullscreen mode

What this does:

  • repoURL: points Argo CD at the Git repository that holds Dockerfiles and manifests.
  • path: tells Argo CD which subdirectory contains the Kubernetes YAML files.
  • automated.prune: removes resources that are no longer defined in the repo.
  • automated.selfHeal: forces a resync if the live state drifts from the declared state.

🔑 Repository Access — SSH vs HTTPS

Argo CD can clone over SSH for private repos; the sshKnownHosts secret must contain the host's fingerprint, which Argo CD validates before establishing the connection, preventing man‑in‑the‑middle attacks.

📋 Sync Policy — Automated vs Manual

Automated sync creates a sync operation for every commit, ensuring immediate rollout. Manual sync requires an explicit UI or CLI trigger, giving operators control over when changes are applied.

Key point: Argo CD’s declarative Application object is the single source of truth for the entire pipeline, eliminating the need for separate deployment scripts.


🛠 Kubernetes Manifests for Python Service — How They Fit

A Deployment defines replica management, which the controller translates into a ReplicaSet that guarantees the requested number of Pods. A Service exposes the pods on a stable cluster IP, and an Ingress routes external traffic to the Service.

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata: name: python-microservice
spec: replicas: 3 selector: matchLabels: app: python-microservice template: metadata: labels: app: python-microservice spec: containers: - name: app image: ghcr.io/example/python-microservice:{{ .Values.imageTag }} ports: - containerPort: 8000 readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 15 periodSeconds: 20
Enter fullscreen mode Exit fullscreen mode

What this does:

  • replicas: ensures three pods are kept running, providing basic HA.
  • readinessProbe / livenessProbe: allow Kubernetes to detect when the service is ready to receive traffic and when it must be restarted.
  • image: uses a templated tag that will be replaced by the GitOps sync step.

    service.yaml

    apiVersion: v1
    kind: Service
    metadata: name: python-microservice
    spec: selector: app: python-microservice ports: - protocol: TCP port: 80 targetPort: 8000 type: ClusterIP

    ingress.yaml

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata: name: python-microservice annotations: nginx.ingress.kubernetes.io/rewrite-target: /
    spec: rules: - host: api.example.com http: paths: - path: / pathType: Prefix backend: service: name: python-microservice port: number: 80

📈 Health Probes — Reducing False Restarts

Readiness probes keep the Service from routing traffic to a pod that hasn't finished its start‑up sequence. Liveness probes interact with the kubelet restart loop, triggering restarts only after a sustained failure, which prevents churn during temporary spikes.

🏷 Image Tag Strategy — Immutable Tags

Argo CD replaces {{ .Values.imageTag }} with a SHA‑256 digest generated by the Docker build step. Because the digest is content‑addressable, each rollout uses an immutable image, eliminating accidental tag reuse. (Also read: ⚙️ Crafting an Argo CD application manifest yaml for FastAPI microservices made easy)

Key point: By separating the Deployment, Service, and Ingress, you can evolve each concern independently while keeping the Git repo as the single source of truth.


🔧 GitOps Workflow — Wiring Everything Together

The GitOps workflow ties Docker image creation, registry push, and manifest update into a single commit that Argo CD will automatically sync. The entire process relies on a single source of truth; any deviation in the cluster triggers a reconciliation loop that restores the declared state.

$ git clone https://github.com/example/python-microservice.git
Cloning into 'python-microservice'...
remote: Enumerating objects: 42, done.
remote: Counting objects: 100% (42/42), done.
remote: Compressing objects: 100% (30/30), done.
Receiving objects: 100% (42/42), 12.3 KiB | 2.46 MiB/s, done.



$ cd python-microservice
$ docker build -t ghcr.io/example/python-microservice:$(git rev-parse -short HEAD) .
Sending build context to Docker daemon 12.34MB
Step 1/5: FROM python:3.11-slim AS builder --> 5d1c8a7c1b7e
Step 2/5: WORKDIR /app --> Running in 8c3e9d6f2a1b
Removing intermediate container 8c3e9d6f2a1b --> a9f0c4d5e6f7
Step 3/5: COPY requirements.txt . --> 3c4b5d6e7f8a
Step 4/5: RUN pip install -no-cache-dir -r requirements.txt --> Running in 9d0e1f2a3b4c
Collecting fastapi==0.95.1
...
Successfully built a9f0c4d5e6f7
Successfully tagged ghcr.io/example/python-microservice:1a2b3c4d



$ docker push ghcr.io/example/python-microservice:1a2b3c4d
The push refers to repository [ghcr.io/example/python-microservice]
e2c3f4d5a6b7: Pushed
...
1a2b3c4d: digest: sha256:7e2f3c5d6a7b8c9d0e1f2a3b4c5d6e7f size: 1572



$ sed -i "s/{{ .Values.imageTag }}/1a2b3c4d/g" k8s/deployment.yaml
$ git add k8s/deployment.yaml
$ git commit -m "ci: update image tag to 1a2b3c4d"
[main 9f8e7d6] ci: update image tag to 1a2b3c4d 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push origin main
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 8 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 1.12 KiB | 1.12 MiB/s, done.
Enter fullscreen mode Exit fullscreen mode

Argo CD detects the new commit, reads the updated deployment.yaml, and applies the new image tag to the running pods. Because the sync policy is set to automated , the change rolls out within seconds without manual intervention.

GitOps reduces drift by ensuring the cluster state is always a direct reflection of the committed Git version.

Key point: The entire pipeline—from Docker build to Kubernetes rollout—is driven by a single Git commit, guaranteeing traceability and reproducibility.


📊 Comparison — Argo CD Application vs Helm‑only Approach

Aspect Argo CD Application Helm‑only (Helm Operator)
Source of truth Git repository containing raw manifests Helm chart stored in a chart repository
Sync granularity Per‑resource drift detection Chart‑level version bump only
Rollback Native Git revert + automatic sync Requires helm rollback command
Policy enforcement Declarative syncPolicy (prune, selfHeal) Limited to Helm hooks

According to the Argo CD documentation, the Application CRD provides fine‑grained control over individual resources, which Helm operators typically cannot achieve without additional tooling. Argo CD evaluates each resource's observedGeneration against the desired manifest, enabling per‑resource health checks.

Key point: Using an Argo CD Application gives you Git‑centric rollbacks and per‑resource health checks, while a pure Helm approach relies on chart versioning alone.


🟩 Final Thoughts

Implementing an Argo CD GitOps pipeline with Dockerized Python microservices consolidates build, test, and deployment into a single, auditable workflow. Keeping Docker image generation and Kubernetes manifest updates inside the same repository eliminates hidden state that often leads to configuration drift. Declarative objects ensure any divergence is automatically corrected, simplifying operational overhead and incident response.

The practical outcome is a repeatable process where a single git push triggers a full end‑to‑end rollout. This reduces the cognitive load of coordinating multiple CI/CD tools and provides a clear audit trail for compliance or debugging. Future extensions can incorporate canary releases, automated security scans, or multi‑cluster synchronization without altering the core GitOps principles.


❓ Frequently Asked Questions

How does Argo CD detect changes in the Docker image?

Argo CD watches the Git repository for commits. When the manifest’s image tag is updated, Argo CD treats the change as a new desired state and applies it to the cluster. The kubelet pulls the referenced image during pod creation.

Can I use a private container registry with this pipeline?

Yes. Create a Kubernetes secret of type docker-registry, reference it in the Deployment’s imagePullSecrets field, and ensure Argo CD has permission to read the secret.

What happens if a deployment fails health checks?

Argo CD’s selfHeal flag will continuously attempt to reconcile the desired state. If the pod remains unhealthy, the Deployment controller keeps recreating pods until the readiness probe succeeds or a manual intervention stops the process.


💡 Want to practise this hands-on? DigitalOcean gives new accounts $200 free credit for 60 days — enough to spin up a full Linux/Docker/Kubernetes environment at no cost.

📚 Recommended reading: Best DevOps & cloud books on Amazon — from Linux fundamentals to Kubernetes in production, curated for working engineers.

📚 References & Further Reading

  • Official Argo CD documentation — comprehensive guide to Application CRDs and sync policies: argo-cd.readthedocs.io
  • Dockerfile best practices — official Docker guidelines for building efficient images: docs.docker.com
  • Kubernetes Ingress documentation — details on routing external traffic to services: kubernetes.io
  • FastAPI – high performance Python web framework — official docs for building ASGI applications: fastapi.tiangolo.com

Top comments (0)