DEV Community

Rizwan Saleem
Rizwan Saleem

Posted on

A Pragmatic Git-Alternatives Workflow: Structured Worktrees, Containerized Repositories, and Atomic

A Pragmatic Git-Alternatives Workflow: Structured Worktrees, Containerized Repositories, and Atomic

A Pragmatic Git-Alternatives Workflow: Structured Worktrees, Containerized Repositories, and Atomic Deploys

If you’re working on a multi-repo project or a polyglot codebase, your version-control workflow can quickly outgrow the traditional fork-and-branch model. This guide presents a cohesive, beginner-to-advanced workflow that combines: 1) Git worktrees for parallel feature work, 2) containerized per-feature repositories to isolate changes and dependencies, and 3) atomic, verifiable deploys with lightweight tooling. You’ll learn how to set up, use, and automate this approach with real-world scenarios, including best practices, caveats, and a minimal toolchain you can adopt today.

Overview

  • Why consider a multi-repo, containerized workflow
  • Core concepts and prerequisites
  • Setting up a multi-repo with worktrees and per-feature repos
  • Building and testing in isolated containers
  • Versioning and releasing with atomic deploys
  • CI/CD considerations and lightweight automation
  • Practical pitfalls and troubleshooting
  • A concrete end-to-end example

1) Why consider a multi-repo, containerized workflow

  • Isolation: Different components or services can live in separate repos with their own dependencies and release cadences.
  • Traceability: Atomic deploys are easier to roll back when every feature or fix is contained in its own containerized environment.
  • Parallel development: Work with multiple worktrees and repositories without polluting the mainline.
  • Reproducibility: Containers ensure consistent build/test environments across machines and CI.

2) Core concepts and prerequisites

  • Git worktrees: Allow multiple checked-out branches (or commits) in a single repository, enabling parallel exploration without stashing or branching overhead.
  • Per-feature repositories: A lightweight pattern where each significant feature or fix gets its own repository (or a monorepo fragment) that can be built into a container image.
  • Containers for isolation: Use Docker or Podman to containerize builds, tests, and runtime environments.
  • Atomic deploys: Deploy a single, self-contained artifact (e.g., a container image) that can be versioned and rolled back atomically.
  • Lightweight tooling: Shell scripts, Makefiles, and a minimal CI/CD pipeline to coordinate across repos and containers.

Prerequisites:

  • Git (2.x)
  • Docker or Podman
  • A central repository (let’s call it “core-project”) containing shared configs, common libraries, or orchestrator scripts
  • Basic familiarity with Dockerfile, docker-compose (optional), and a simple CI system (GitHub Actions, GitLab CI, etc.)

3) Setting up a multi-repo with worktrees and per-feature repos
Goal: Work on two features in parallel, each in its own containerized environment, while keeping the core project stable.

  • Step 1: Initialize the core repository

    • Create core-project as the central repo containing shared code and deployment orchestration.
    • Structure example:
    • core-project/
      • services/
      • service-a/
      • service-b/
      • infra/
      • docs/
      • deploy/
    • Commit a minimal baseline.
  • Step 2: Create a feature worktree in the core repo

    • Use Git worktree to check out a separate branch for a feature without creating a nested clone.
    • Commands:
    • git worktree add ../core-feature-a feature/a-awesome-feature
    • cd ../core-feature-a
    • Edit files for feature a
    • git commit -m "Start feature A in dedicated worktree"
    • Benefits: Independent development, easy switching between features, no dirty working trees in main.
  • Step 3: Create per-feature repositories (optional but powerful)

    • Create a minimal per-feature repo that contains only the code and configs for that feature, plus a Dockerfile to build a container image.
    • Example:
    • feature-a-repo/
      • app/
      • Dockerfile
      • tests/
    • This allows independent security reviews, access control, and release cycles per feature.
  • Step 4: Link core and feature repos

    • Use submodules or a lightweight manifest to keep track of per-feature repos from the core project.
    • Submodules approach:
    • In core-project, add: git submodule add services/feature-a
    • Commit the submodule reference.
    • Manifest approach (a simple YAML):
    • core-project/feature-manifest.yml
    • Tools can parse manifest to orchestrate builds.
  • Step 5: Standardize build definitions

    • Each feature repo includes:
    • Dockerfile to build the runtime image
    • docker-compose.yml (optional) to wire services for local testing
    • A small test suite (unit/integration)
    • Ensure consistent base images and tooling versions across features to simplify CI.

4) Building and testing in isolated containers

  • Local container workflow

    • Build feature container:
    • docker build -t feature-a:0.1.0 ./feature-a-repo
    • Run tests inside container:
    • docker run rm feature-a:0.1.0 pytest tests
    • Run the service locally (optional):
    • docker run -d name feature-a -p 8080:80 feature-a:0.1.0
    • curl http://localhost:8080/health
  • Reproducible environments

    • Pin dependencies with exact versions in the feature Dockerfiles (e.g., pip install -r requirements.txt with pinned versions, npm ci).
    • Use multi-stage Docker builds to keep final images small.
  • Isolating CI jobs

    • Each feature’s CI pipeline builds and tests only its own container image.
    • Artifacts produced: container image tags (e.g., registry.example.com/core/feature-a:0.1.0).
  • Local verification script (example in Bash)

    • A simple script that builds, tests, and runs health checks:
    • #!/bin/bash
    • set -euo pipefail
    • IMAGE_TAG=${IMAGE_TAG:-feature-a:0.1.0}
    • docker build -t $IMAGE_TAG ./feature-a-repo
    • docker run rm -d name feature-a -p 8080:80 $IMAGE_TAG
    • sleep 2
    • if curl -sS http://localhost:8080/health | grep -q "ok"; then echo "Health OK"; else echo "Health check failed"; exit 1; fi
    • docker stop feature-a

5) Versioning and releasing with atomic deploys

  • Versioned container images

    • Use semantic versioning: A.B.C for each feature release (e.g., feature-a:1.2.0)
    • Tag images with build metadata if needed: feature-a:1.2.0+build.123
  • Atomic deploy principle

    • Deploy a single, immutable container image that represents the feature’s state.
    • Use a small orchestrator that handles production deploys by updating a single image tag or a Kubernetes/Compose service to a new image.
    • Rollback is as simple as rolling back to the previous image tag, with health checks ensuring traffic shifts only on success.
  • Deployment examples

    • Docker Compose (local or simple edge deployments):
    • In a deploy/ directory, maintain docker-compose.yml that uses image: registry/core/feature-a:1.2.0
    • docker-compose up -d
    • Kubernetes (production-ish):
    • Deploy manifests reference the specific image tag
    • Use a canary or blue/green pattern by flipping a service's target image tag
    • Implement readiness probes to ensure the new version passes health checks before shifting traffic
  • Changelog and release notes

    • Maintain a simple CHANGELOG.md per feature or in core-project.
    • Include the feature scope, breaking changes, and migration notes for downstream consumers.

6) CI/CD considerations and lightweight automation

  • Central automation flow

    • When a feature repo is updated, CI builds a container image and runs tests.
    • Upon success, CI publishes the image to a registry with a deterministic tag.
    • Core repo’s deployment orchestrator picks up the new image tag and triggers an atomic deploy.
  • Lightweight tooling

    • Use a small Makefile or a Python script to coordinate:
    • Build: docker build
    • Test: docker run tests
    • Push: docker push
    • Deploy: run a deployment script that updates the running service
  • Example Makefile (high-level)

    • IMAGE=registry/core/feature-a
    • VERSION=1.2.0
    • build:
    • docker build -t $(IMAGE):$(VERSION) .
    • test:
    • docker run rm $(IMAGE):$(VERSION) pytest
    • push:
    • docker push $(IMAGE):$(VERSION)
    • deploy:
    • ./deploy/atomic-deploy.sh $(IMAGE):$(VERSION)
  • GitHub Actions (sketch)

    • On push to feature-a repo: build, test, push image
    • On tag: trigger core-project deployment to update the manifest and trigger rollout
    • Use environment protection and approvals for production deployments

7) Practical pitfalls and troubleshooting

  • Pitfall: drift between core and feature repos
    • Mitigation: enforce a clear contract in a manifest; pin submodule commits; run cross-repo integration tests in CI.
  • Pitfall: slow builds due to large base images
    • Mitigation: use slim or alpine variants; multi-stage builds; cache dependencies aggressively.
  • Pitfall: inconsistent environments
    • Mitigation: lock down base images with digests; use same Docker version across environments.
  • Pitfall: flaky tests in containers
    • Mitigation: run tests with deterministic timeouts; isolate external dependencies; provide local mock services.

8) A concrete end-to-end example

  • Scenario: You’re adding a new search feature to a web app, with a focused backend service and a frontend component.
  • Step-by-step:
    • Create feature-a-repo: backend-search
    • Dockerfile builds a Python FastAPI app with pinned dependencies
    • tests/ includes unit tests for search logic
    • Create core-project and wire feature-a via submodule or manifest
    • In core-project, add a small orchestrator script that, when a new feature image is available, updates a Kubernetes Deployment to the new image tag
    • CI builds feature-a image: registry.example.com/core/backend-search:1.0.0
    • CI runs tests inside container; passes
    • Deploy: orchestration updates the Deployment to use backend-search:1.0.0; health checks verify search endpoint
    • Rollback: if health checks fail after the update, revert to previous image tag

9) Quick-start checklist

  • Decide on whether to use per-feature repos, submodules, or a manifest approach
  • Set up an initial core-project with a minimal deploy orchestration
  • Create first feature repo with a Dockerfile and tests
  • Implement a simple build/test/deploy script or Makefile
  • Establish tagging and versioning conventions
  • Configure CI to automate builds, tests, and deployments
  • Document the workflow for the team and enforce it with checks in CI

Illustration: The workflow in one glance

  • Core-project acts as the conductor, hosting shared logic and the orchestrator
  • Each feature has its own isolated container image; you can run, test, and verify independently
  • Deploys are atomic: the entire feature ships as a single versioned container
  • Rollbacks are trivial by reapplying the previous image tag

Would you like me to tailor this workflow to a specific tech stack (e.g., Node + NestJS services, Python microservices, or Go services) and provide concrete code snippets for Dockerfiles, a manifest schema, and a minimal CI pipeline?

-

Rizwan Saleem | https://rizwansaleem.co

Sources

Top comments (0)