DEV Community

Oleksandr Kuryzhev
Oleksandr Kuryzhev

Posted on Originally published at kuryzhev.cloud

GitOps with Gitpod vs Local Dev Containers: Which to Pick

Originally published on kuryzhev.cloud


A new engineer clones the infrastructure repo, spends a day fighting mismatched Helm, kubectl and Flux versions, and finally gets a cluster that almost matches staging. Two weeks later a reviewer cannot reproduce a bug from a pull request because their local tooling drifted. GitOps with Gitpod targets exactly this gap: the development environment is declared in Git, built on demand, and thrown away when the branch is done.

The honest question is whether a hosted workspace is the right way to get that, or whether a local dev container from the same repo is enough. This article compares the two classes of approach and ends with conditions, not a universal winner.

When GitOps with Gitpod matters

GitOps already says the cluster's desired state lives in Git. The development environment is usually the exception: it lives in wikis, shell history and someone's laptop. Closing that gap matters most in a few situations.

  • Several people change the same manifests, Helm charts or Terraform modules, and "works on my machine" shows up in review.
  • Onboarding requires credentials and tooling that are hard to install on managed laptops.
  • Reviewers need to run a pull request, not just read the diff.
  • Contractors or temporary staff need access without data landing on personal hardware.

If you are a solo maintainer with one stable toolchain, none of this pays for itself. The payoff grows with team size and with the number of tools that must agree on versions.

Watch out for product naming. Gitpod rebranded to Ona in 2025, and the older "Classic" workspace model was retired in favor of the newer architecture, which can run in your own cloud account. Configuration formats differ between generations, so verify the current file names and runner options in the official Gitpod/Ona documentation before copying any older example you find online.

Option A: Hosted workspaces (Gitpod-style)

In this class, a platform builds a container from your repo on demand, gives you a browser or remote-IDE session, and destroys it afterward. The environment definition is committed next to the code, so a branch and its tooling travel together. Most current platforms in this class read the open dev container format, which also keeps Option B available as a fallback.

Pros

  • Identical environments by construction. Everyone starts from the same image and bootstrap steps, pinned by the commit.
  • Fast review. A pull request can open directly into a running workspace with the branch checked out.
  • Thin clients. Builds, cluster emulation and secrets stay off the laptop, which helps with locked-down or low-spec machines.
  • Central policy. Network egress, allowed images and secret scopes can be governed in one place, especially when runners live in your own cloud account.
  • Prebuilds. Dependencies and images can be built ahead of time so a workspace starts without a long install step (verify current prebuild behavior in the docs).

Cons

  • Running cost and quotas. On usage-based plans, idle workspaces typically keep consuming credits or compute until they time out; cost control becomes an operational task.
  • Network dependency. Latency and outages affect every developer at once.
  • Nested container limits. Running kind or k3d inside a workspace needs Docker-in-Docker or a privileged runtime, which some platforms restrict.
  • Vendor and data-residency questions. Source code and secrets pass through a third party unless you self-host runners.
  • Product churn. Renames and model changes, as noted above, mean you must track the vendor roadmap.

Option B: Local dev containers from the same repo

The alternative is to commit the same devcontainer.json and let each developer run it locally with Docker or a compatible engine. You get the "environment as code" benefit without a hosted service. The specification is open and documented at containers.dev, and GitHub's hosted equivalent is covered in the Codespaces documentation.

Here is a minimal definition that works locally and on any hosted platform that permits the privileged container Docker-in-Docker requires. It installs Docker-in-Docker, kubectl, Helm and kind, then runs a bootstrap script after creation. The file is parsed as JSON with comments, so the inline comments are valid.

{
  "name": "gitops-dev",
  "image": "mcr.microsoft.com/devcontainers/base:ubuntu",
  "features": {
    // nested Docker so kind can create a cluster inside the workspace
    "ghcr.io/devcontainers/features/docker-in-docker:2": {},
    "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {
      "minikube": "none"   // kind is used instead
    },
    // community feature that installs the kind CLI
    "ghcr.io/devcontainers-extra/features/kind:1": {}
  },
  // runs once after the container is created
  "postCreateCommand": "bash .devcontainer/bootstrap.sh"
}

Pros

  • No recurring platform bill and no third party in the code path.
  • Works offline once images are cached.
  • Low latency for editors and local file watchers.
  • Portable. The same definition can move to a hosted service later.

Cons

  • Hardware variance. A kind cluster plus controllers needs real CPU and memory; underpowered laptops struggle.
  • Host drift leaks in. Docker engine versions, file-sharing performance on macOS and Windows, and CPU architecture (arm64 vs amd64) still differ per machine.
  • Credentials on endpoints. Cloud tokens and kubeconfigs sit on laptops unless you add short-lived auth.
  • No instant PR review without a checkout and a container rebuild.

Decision matrix

Score your own constraints against this table. Neither column wins on every row, and that is the point.

Constraint Hosted workspaces Local dev containers
Uniform environment across the team Strong Good, with host drift
Onboarding time Fast if prebuilt; depends on image size Depends on laptop and Docker setup
Running PRs for review Strong Manual
Cost predictability Usage-based, needs limits Fixed (hardware you own)
Secrets kept off endpoints Strong Weak without extra tooling
Offline or high-latency work Poor Strong
Nested Kubernetes (kind/k3d) Platform-dependent Usually fine
Vendor independence Lower (higher if self-hosted runners) High

Watch out for the nested-cluster row. It is a frequent surprise. Confirm that your chosen platform permits the privileges kind needs before you design the workflow around it.

Evidence-based recommendation

Start by writing the environment as a dev container definition in the repo. That step is common to both options, costs little, and removes tool-version drift on its own, although host-level differences such as CPU architecture and Docker engine version remain for local use. Then decide where it runs.

  • Choose hosted workspaces when you have a team larger than a handful, strict control over credentials, frequent reviewers who need to run branches, or laptops that cannot carry a local cluster.
  • Choose local dev containers when hardware is adequate, work happens offline or on slow links, budget for a platform is absent, or source code cannot leave your network.
  • Use both when you can. A shared definition lets heavy reviewers use hosted workspaces while regular contributors stay local.

To make the environment genuinely GitOps-shaped, have the bootstrap script create a throwaway cluster and point a reconciler at the current branch. The following script uses the Flux CLI; the Argo CD equivalent works on the same idea. The repository URL and path are placeholders for your own, and the branch must already exist on the remote.

#!/usr/bin/env bash
set -euo pipefail

BRANCH="$(git rev-parse --abbrev-ref HEAD)"
if [ "$BRANCH" = "HEAD" ]; then
  # PR workspaces often check out a detached commit
  echo "Detached HEAD: check out a branch that exists on the remote" >&2
  exit 1
fi
REPO_URL="https://github.com/<org>/<infra-repo>"   # replace with your repo

kind create cluster --name dev          # disposable local cluster
curl -s https://fluxcd.io/install.sh | sudo bash   # pin a version in real use
flux install                             # controllers only, no bootstrap commit

# private repo only: create a read-only credential and add
# --secret-ref=dev-auth to the source command below
# flux create secret git dev-auth --url="$REPO_URL" \
#   --username=git --password="<read-only-token>"

# reconcile the branch you are working on, not main
flux create source git dev \
  --url="$REPO_URL" --branch="$BRANCH" --interval=1m

flux create kustomization dev \
  --source=GitRepository/dev \
  --path=./clusters/dev \
  --prune=true --interval=1m

Because the source tracks the checked-out branch, pushing a commit updates the workspace cluster on the next reconcile, within about one interval. This is not real-time: the controllers pull from the remote, so uncommitted or unpushed local edits are not applied. The benefit is that the dev cluster follows Git rather than manual kubectl apply. Flux details and current API versions are in the Flux documentation.

Watch out for secrets. A branch-tracking workspace that pulls private repos needs a deploy key or token, supplied through the --secret-ref step shown above. Scope it read-only, inject it at runtime from the platform's secret store rather than baking it into the image, and never commit it to make the bootstrap "just work."

One caveat applies to any claim about speed or cost in this space: it depends on image size, prebuild configuration and plan terms, so measure your own startup time and monthly usage in a pilot. For more infrastructure comparisons and GitOps patterns, browse kuryzhev.cloud.

The bottom line: define the environment in Git first, pilot a hosted runner with a small group that has the strongest need, and keep the local path working so you are never locked to one vendor's roadmap.

Related

Top comments (0)