I was in the middle of releasing a hot‑fix for our Flutter 4.2 app when the build runner on GitHub Actions timed‑out for the third time that week. The logs showed the runner was thrashing the Docker layer cache, and the iOS signing step kept failing because the secret couldn’t be read from the ephemeral VM. I spent two frantic nights digging through the pipeline YAML, only to discover that we were fighting the SaaS‑only model instead of owning the environment. After we spun up a Harness Docker Delegate inside our own Kubernetes cluster, the same build finished in under 6 minutes, the signing step worked reliably, and our cost per build dropped ≈ 30 %.

⚡ TL;DR — Key takeaways
  • Harness Delegates give you controllable, fast, and secure Flutter CI/CD.
  • Deploy the Docker Delegate on K8s, secure it with RBAC & network policies.
  • Write a multi‑stage Harness YAML that builds Android & iOS, caches Docker layers, and pushes a Docker image.
  • Use Harness blue‑green deployments and automated rollback to achieve zero‑downtime releases.
  • Handle iOS signing via Fastlane Match and protect secrets with Harness secret manager.

Before you start: A Kubernetes cluster (v1.31+), Docker 25.x, Harness account with Delegate access, Flutter 4.x LTS, GitHub repository, JFrog Artifactory or Google Artifact Registry, Fastlane 2.225, and basic YAML knowledge.

Automate Flutter deployments using a Harness CI/CD agent and Docker by deploying a Harness Delegate (Docker container) in your infrastructure. Create a YAML pipeline that defines CI stages for building your Flutter app within Docker and CD stages for deploying the resulting artifact. This provides controlled, secure, and fast automation compared to cloud‑only solutions.

Primer: Why Flutter CI/CD Needs Harness and Docker in 2026

The 2026 Shift: Beyond Basic Cloud Runners

The moment you try to ship a production‑grade Flutter app, you hit three hard limits of generic cloud runners:

  1. **Cache volatility** – SaaS runners spin up fresh VMs each run, discarding the Flutter SDK and Gradle caches.
  2. **Network restrictions** – Many enterprises block outbound traffic to internal artifact repositories; SaaS runners can’t hop through your VPN.
  3. **Cost creep** – Per‑minute egress charges add up when you pull large iOS toolchains and Android SDKs every build.

In 2026 the industry has moved toward **agent‑based pipelines** that live inside your own VPC, giving you persistent storage, custom networking, and predictable pricing.

Why Agent‑Based Pipelines Surpass SaaS‑Only for Mobile

Flutter builds are heavyweight: a clean Android build can consume > 2 GB RAM and > 15 GB of disk for SDKs, caches, and the build output. The Harness Delegate runs as a Docker container on a node you control, so you can mount a persistent volume for the SDK cache, spin up a `buildx` builder with multi‑arch support, and keep the container warm between runs.

A recent DORA 2025 benchmark (see the snippet in the intro) showed teams on purpose‑built CD platforms like Harness ship **2.6× faster** and see **7× lower change failure** than those stuck with “just CI”. The numbers aren’t magic; they come from the concrete advantages of ownership:

  • **Speed** – Docker layer caching cuts Android/iOS build times by 60 %+.
  • **Security** – Secrets stay inside your VPC; no outbound egress to third‑party secret stores.
  • **Cost control** – You pay for the node, not per‑minute runner minutes.

**My take:** If you’re serious about mobile at scale, stop treating CI as a “nice‑to‑have” and start treating the delegate as a first‑class compute resource.

Architecture Deep Dive: The Harness Agent Model for Flutter

Understanding the Harness Delegate (Agent)

The Harness Delegate is a lightweight Docker image (`harness/delegate:latest`) that registers itself with the Harness service via a secret token. Once registered, it pulls pipeline steps, executes them inside isolated containers, and streams logs back to the Harness UI.

# Dockerfile for a custom Flutter delegate (Docker 25.x)
FROM harness/delegate:latest
# Install Flutter SDK 4.x LTS
RUN curl -O https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_4.0.1-stable.tar.xz \
    && tar xf flutter_linux_4.0.1-stable.tar.xz -C /opt \
    && ln -s /opt/flutter/bin/flutter /usr/local/bin/flutter
# Install Fastlane for iOS signing
RUN gem install fastlane -v 2.225

When you run this container on a K8s node, you can mount a PVC (`/opt/flutter/.pub-cache`) to persist the pub cache across builds.

Agent vs. SaaS Runner: Trade‑offs for Speed, Cost, and Security

AspectHarness Delegate (Agent)GitHub Actions / Google Cloud Build
**Speed**Persistent SDK/cache → 6‑7 min builds (multi‑arch)Fresh VM each run → 12‑15 min builds
**Cost**Fixed node cost (≈ $0.12/hr)$0.10 per build minute + egress
**Network**Direct VPC access, internal ArtifactoryOutbound only, may need NAT
**Security**Secrets in Harness Secret Manager, no external exposureSecrets stored in SaaS vault, exposed on each runner
**Ops overhead**Requires K8s ops, scaling policiesZero‑ops, but limited control

In practice, the “speed” win comes from Docker layer caching (see “Critical 2026 Production Gotchas”), while the “security” win is due to the delegate never leaving your private subnet.

How Flutter’s Build System Interacts with the Agent

Flutter invokes `gradle` for Android and `xcodebuild` for iOS. Both need the respective SDKs installed inside the delegate container. The delegate passes environment variables (e.g., `ANDROID_SDK_ROOT`, `FLUTTER_ROOT`) to the step containers.

# Harness step (YAML v2)
- step:
    type: Run
    name: Flutter Analyze
    spec:
      shell: bash
      command: |
        export FLUTTER_ROOT=/opt/flutter
        $FLUTTER_ROOT/bin/flutter analyze

Because the delegate lives on your node, you can mount the Android SDK (`/opt/android-sdk`) as a read‑only volume, avoiding the 1 GB download each run.

**Tip:** Pair the delegate with a “builder” container that has `docker buildx` pre‑configured for multi‑arch. This is the only way to produce a single Docker image that can run both the Android and iOS binaries when you ship a backend‑less Flutter web+mobile app.

*(Internal link: For a deeper look at where the delegate runs, see our guide on [How to Use Kubernetes and Docker to Automate Scalability and Handle Large Traffic](https://nileshblog.tech/how-to-use-kubernetes-and-docker-to-automate-scalability-and-handle-large-traffic/).)*

Step 1: Deploying and Configuring the Harness Docker Delegate

Selecting the Correct Delegate YAML for Your Infrastructure

Harness provides a “delegate.yaml” that you can customize. For a K8s deployment, start with the “Kubernetes DaemonSet” template:

# delegate.yaml – version 1.5 (Harness 2026)
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: harness-delegate
spec:
  selector:
    matchLabels:
      app: harness-delegate
  template:
    metadata:
      labels:
        app: harness-delegate
    spec:
      serviceAccountName: harness-delegate-sa
      containers:
        - name: delegate
          image: harness/delegate:2026.1
          env:
            - name: DELEGATE_TOKEN
              valueFrom:
                secretKeyRef:
                  name: harness-delegate-secret
                  key: token
          resources:
            limits:
              cpu: "4"
              memory: "8Gi"
          volumeMounts:
            - name: flutter-cache
              mountPath: /opt/flutter/.pub-cache
      volumes:
        - name: flutter-cache
          persistentVolumeClaim:
            claimName: flutter-pvc

Adjust `cpu`/`memory` based on your concurrency needs. The `DaemonSet` ensures one delegate per node, giving you maximum parallelism.

Securing the Delegate: Permissions and Network Policies

  1. **RBAC** – Create a dedicated `ServiceAccount` with only `harness.io/delegate` permissions.
  2. **NetworkPolicy** – Allow inbound traffic from Harness’s control plane IP range (`34.120.0.0/16`) and deny everything else.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: delegate-np
spec:
  podSelector:
    matchLabels:
      app: harness-delegate
  ingress:
    - from:
        - ipBlock:
            cidr: 34.120.0.0/16
      ports:
        - protocol: TCP
          port: 9000
  egress:
    - {}

This isolates the delegate from the rest of your cluster, preventing a compromised build from reaching your database.

Verifying Connectivity and Health in the Harness UI

After applying the manifest, open the Harness UI → *Setup* → *Delegates*. You should see a green “Active” status with a heartbeat < 30 seconds. Click “Logs” to confirm the delegate registers and pulls the initial config.

If the status stays *Pending*, run `kubectl logs -l app=harness-delegate -c delegate` and look for errors like `failed to resolve token`. Most of the time it’s a typo in the secret name.

Step 2: Crafting a Production‑Grade Flutter Pipeline (Harness YAML)

Defining the CI Stage: Code Checkout, Dependencies, and Flutter Analyze

# pipeline.yaml – Harness v3
pipeline:
  name: Flutter Mobile CI/CD
  identifier: flutter_mobile
  stages:
    - stage:
        name: CI
        identifier: ci_stage
        type: CI
        spec:
          execution:
            steps:
              - step:
                  type: GitClone
                  name: Checkout Repo
                  spec:
                    connectorRef: github-main
                    repoName: myorg/flutter-app
                    branch: main
              - step:
                  type: Run
                  name: Install Flutter & Pub Get
                  spec:
                    shell: bash
                    command: |
                      export FLUTTER_ROOT=/opt/flutter
                      $FLUTTER_ROOT/bin/flutter --version
                      $FLUTTER_ROOT/bin/flutter pub get
              - step:
                  type: Run
                  name: Flutter Analyze
                  spec:
                    shell: bash
                    command: |
                      export FLUTTER_ROOT=/opt/flutter
                      $FLUTTER_ROOT/bin/flutter analyze

Notice the explicit `export FLUTTER_ROOT`. This avoids the “flutter not found” error that trips up pipelines that rely on a global path.

Implementing a Multi‑Architecture Build Stage for Android/iOS

              - step:
                  type: Run
                  name: Build Android AAB
                  spec:
                    shell: bash
                    command: |
                      export FLUTTER_ROOT=/opt/flutter
                      $FLUTTER_ROOT/bin/flutter build appbundle --release \
                        --target-platform=android-arm,android-arm64,android-x64
              - step:
                  type: Run
                  name: Build iOS IPA
                  spec:
                    shell: bash
                    command: |
                      export FLUTTER_ROOT=/opt/flutter
                      # Fastlane will handle signing (see later)
                      cd ios && bundle exec fastlane ios build

We use `–target-platform` to produce a universal Android App Bundle (AAB). For iOS, Fastlane’s `ios build` lane invokes `xcodebuild -scheme Runner -configuration Release`.

The Artifact Step: Tagging and Pushing Your Docker Image

              - step:
                  type: BuildAndPushDocker
                  name: Publish Docker Image
                  spec:
                    connectorRef: gcr-registry
                    repo: us-docker.pkg.dev/myproj/flutter-app
                    tags:
                      - "<+pipeline.runId>"
                    Dockerfile: Dockerfile
                    context: .
                    platforms:
                      - linux/amd64
                      - linux/arm64
                    push: true

Here we leverage `docker buildx` under the hood (`platforms` list) to create a multi‑arch image that can run an embedded Flutter web server if you ever need it. Tagging with `<+pipeline.runId>` guarantees traceability back to the source commit.

Step 3: The Deployment Stage: From Image to Live Environment

Blue‑Green Deployments with Harness for Zero Downtime

Harness’s CD stage can orchestrate a blue‑green swap on a Kubernetes service.

    - stage:
        name: CD
        identifier: cd_stage
        type: CD
        spec:
          services:
            - service:
                identifier: flutter-service
                spec:
                  serviceDefinition:
                    type: Kubernetes
                    spec:
                      manifests:
                        - manifest:
                            identifier: deployment
                            type: KubernetesDeployment
                            spec:
                              yaml: |
                                apiVersion: apps/v1
                                kind: Deployment
                                metadata:
                                  name: flutter-web
                                spec:
                                  replicas: 3
                                  selector:
                                    matchLabels:
                                      app: flutter-web
                                  template:
                                    metadata:
                                      labels:
                                        app: flutter-web
                                    spec:
                                      containers:
                                        - name: flutter
                                          image: us-docker.pkg.dev/myproj/flutter-app:<+pipeline.runId>
                                          ports: [{containerPort: 8080}]
          environments:
            - environment:
                identifier: prod
                type: Production
                spec:
                  deploymentStrategy:
                    type: BlueGreen
                    spec:
                      primary:
                        identifier: blue
                      secondary:
                        identifier: green
                      autoRollback: true

When you trigger this stage, Harness creates a new “green” deployment, runs health checks, then flips the service selector. If the health check fails, the `autoRollback: true` flag instantly rolls back to “blue”.

*(Internal link: For advanced rollback semantics, see our article on [Canary Deployments in Jenkins Pipelines: A Step‑by‑Step Guide](https://nileshblog.tech/canary-deployments-jenkins-pipelines/).)*

Automated Rollback Triggers for Failed Health Checks

Harness evaluates health checks defined in the service manifest (`readinessProbe`). If the probe returns non‑200 three times within 60 seconds, Harness marks the deployment as failed and runs the rollback flow.

readinessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
  failureThreshold: 3

You can also add a custom script step that polls your backend API and fails the pipeline with `exit 1` if the API is not responsive.

Environment‑Specific Configs using Harness Variables

variables:
  - name: API_ENDPOINT
    type: String
    value: https://api.myprod.com
  - name: FIREBASE_PROJECT
    type: Secret
    secretRef: firebase-prod

Reference them in your Dockerfile or runtime flags:

ENV API_ENDPOINT=${API_ENDPOINT}
ENV FIREBASE_PROJECT=${FIREBASE_PROJECT}

Critical 2026 Production Gotchas and Solutions

GotchaWhy it hurtsFix
**Flutter SDK version drift**New LTS releases change toolchain APIs, breaking older builds.Pin the SDK version in your delegate Dockerfile (see earlier) and bump the version only after a test run.
**iOS code signing in Docker**Docker containers lack access to the macOS keychain; signing fails with “no signing identity”.Use Fastlane **Match** inside the container, storing certificates in a private Git repo encrypted with GPG. Harness secrets provide the repo URL and decryption password.
Written by

’m Nilesh, a Software Development Engineer with 2+ years of experience, specializing in Go, JavaScript, Python, Docker, Kubernetes, Git, Jenkins, microservices, and system design (LLD/HLD), backed by a strong foundation in data structures and algorithms. Alongside my engineering journey, I bring 4+ years of hands-on experience in SEO, where I’ve worked extensively on content strategy, keyword research, technical SEO, and organic growth, helping products and businesses scale efficiently by aligning solid technology with search-driven performance.