Application Repositories as Golden Paths: Dockerfile, CI, and .k8s in Every Repo
Our platform manages where and how services are deployed through GitOps repositories. But what about the application itself — the code, the build, the container? Until now, every team set up their own Dockerfile, wrote their own CI pipeline, and figured out their own image tagging strategy. The result was predictable: 10 teams, 10 different CI workflows, 10 different container build approaches, and zero consistency.
The Application Repository convention changes this. When a new service is scaffolded via Backstage, it delivers a complete, opinionated repository with source code, Dockerfile, CI pipeline, and a .k8s folder — giving the application team autonomy over their workload configuration while keeping the platform in control of the deployment model.
The architecture: two repos, one deployment
Our GitOps model uses a clear separation between deployment configuration and application configuration:
{domain}-gitops/k8s/{env}/{service}/ ← Platform-managed base manifests
{app}-repo/.k8s/values.yaml ← Developer-managed overrides
The domain GitOps repository contains the foundational Kubernetes configuration — Deployments, Services, NetworkPolicies, HPA, PDB — generated by the create-service template and maintained by the domain team's platform conventions.
The application repository contains a .k8s folder where the development team manages values that change frequently with application releases.
ArgoCD's Multiple Sources pattern merges both sources at sync time. The base manifests from the GitOps repo provide structure and safety. The application overrides provide flexibility and speed.
What developers can override
The .k8s/values.yaml in the application repository gives developers control over:
- Image tag: Updated automatically by CI on every successful build
- Replicas: Override min/max HPA thresholds for specific workload needs
- CPU and memory: Adjust requests and limits as the application's profile evolves
- Environment variables: Application-specific configuration that changes with releases
These are the values that change most frequently and are most directly tied to the application code. By keeping them in the app repo, developers don't need a PR on the GitOps repo every time they push a release.
What developers don't touch
The base manifests in {domain}-gitops/k8s/ remain under platform convention control:
- Namespace definition with all 9 required labels
- Security context:
runAsNonRoot,readOnlyRootFilesystem,capabilities: drop: [ALL] - NetworkPolicy: project-boundary isolation
- PodDisruptionBudget: prod-only safety net
- ResourceQuota: env-sized limits
This separation ensures that security and compliance guarantees are never accidentally overridden by a developer who "just needs to change the memory limit."
The CI pipeline: from commit to deploy
Every application repository scaffolded by create-service includes a pre-configured GitHub Actions workflow. The pipeline is not something the team needs to write — it arrives ready:
Developer pushes code
│
▼
┌─── CI Pipeline ────────────────────────────────────┐
│ │
│ 1. Test & Lint Run unit tests, lint checks │
│ 2. Build Build container from Dockerfile│
│ 3. Publish Push image to container registry│
│ 4. Tag Update Update .k8s/values.yaml with │
│ new image tag, commit back │
│ │
└─────────────────────────────────────────────────────┘
│
▼
ArgoCD detects change in app repo (secondary source)
│
▼
Deployment syncs with new image tag
The key step is Tag Update. After publishing the container image, the pipeline automatically updates image.tag in .k8s/values.yaml and commits the change back to the application repository. This commit is what ArgoCD watches.
Because ArgoCD is configured with Multiple Sources — one pointing at the GitOps repo for base manifests, one pointing at the app repo for overrides — the new tag triggers an automatic sync to the target environment.
No manual image tag updates. No separate "deploy" step. Push code, get deployment.
The Dockerfile: secure by default
The scaffolded Dockerfile follows platform security standards:
- Multi-stage build: Separate build and runtime stages to minimize image size
- Non-root user:
USER 1000— matches the pod security context (runAsNonRoot: true) - Read-only filesystem compatible: Application writes go to
/tmp(mounted asemptyDir) - Language-specific optimization: Node.js, .NET, and Python each get a tailored Dockerfile with appropriate base images and dependency caching
The developer does not need to know about pod security contexts or read-only filesystems. The Dockerfile and the Kubernetes manifests are designed to work together from day one.
The full picture
When create-service runs, the developer gets:
| What | Where | Who maintains it |
|---|---|---|
| Source code boilerplate | {app}-repo/src/ | Application team |
| Dockerfile | {app}-repo/Dockerfile | Application team |
| CI pipeline | {app}-repo/.github/workflows/ci.yaml | Platform team (generated), application team (extended) |
| Workload overrides | {app}-repo/.k8s/values.yaml | Application team |
| TechDocs | {app}-repo/docs/ | Application team |
| Base K8s manifests | {domain}-gitops/k8s/{env}/{service}/ | Domain team |
| ArgoCD routing | platform-gitops/argocd/applicationsets/ | Platform team |
Three repositories, three ownership boundaries, one deployment that works without manual coordination.
Why not everything in the app repo?
A common question: "Why not put all Kubernetes manifests in the application repository?"
Because it breaks the ownership model:
- Security context and network policies are platform concerns, not application concerns. Developers should not need to (or be able to) modify them.
- Multiple services share the same ApplicationSet and domain structure. Centralizing this in the GitOps repo avoids duplication and ensures consistency.
- Convention validation runs on the GitOps repo. CI checks for naming, labels, and security defaults happen where those definitions live.
The .k8s folder gives developers the autonomy they need — image tags, scaling, resources — without giving them access to the things they should not change.
The Application Repositories convention is documented in the Platform Convention — Application Repositories. The ArgoCD Multiple Sources pattern is in the ArgoCD Convention.