DEV Community

Cover image for From Docker Compose on My Laptop to OKE in Production — Same App, Zero Rewrites
Pavan Madduri
Pavan Madduri

Posted on

From Docker Compose on My Laptop to OKE in Production — Same App, Zero Rewrites

I have a rule: if I can't run the full stack on my laptop with docker compose up, the architecture is too complicated.

But then you need to deploy to production, and suddenly you're rewriting everything as Kubernetes manifests. The Compose file that worked on your machine is useless. Config lives in two places and they drift apart.

Here's the workflow I settled on after trying a bunch of things that didn't work well.

The Local Stack

Standard web app — API, Redis, Postgres. Three services.

# docker-compose.yml
services:
  api:
    build: .
    ports:
      - "8080:8080"
    environment:
      - DATABASE_URL=postgres://app:secret@db:5432/myapp
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 10s

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "app"]

  cache:
    image: redis:7-alpine

volumes:
  pgdata:
Enter fullscreen mode Exit fullscreen mode

docker compose up and the full stack is running. No external dependencies, fast iteration.

What I Tried and Abandoned

Komposekompose convert technically works but the output is ugly. Tons of annotations, weird formatting, needs so much cleanup I might as well write the YAML by hand.

Docker Compose on Kubernetes — Various tools that try to run Compose files directly on K8s. They all add complexity and break in subtle ways.

Trying to share one config — I wasted a weekend trying to make the same file work for both. Local dev and production have genuinely different requirements. Pretending otherwise creates worse problems.

What Actually Works: Convention Over Tooling

I keep two config sets, aligned by convention:

project/
├── docker-compose.yml          # Local dev
├── Dockerfile
├── k8s/
│   ├── base/
│   │   ├── deployment.yaml
│   │   ├── service.yaml
│   │   └── kustomization.yaml
│   └── overlays/
│       ├── staging/
│       └── production/
└── Makefile
Enter fullscreen mode Exit fullscreen mode

Same image names, same env var names, same port numbers in both places. When I change a port in Compose, I grep for it in k8s/ and update it. Manual, but nothing breaks silently.

The key differences between local and OKE:

  • Database — Container locally, OCI managed service in production. I don't run databases on K8s.
  • Secrets — Plain text in Compose, OCI Vault via External Secrets Operator on OKE.
  • Scaling — One replica locally, HPA on OKE.

The K8s Side

# k8s/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: iad.ocir.io/mytenancy/myapp:latest
          ports:
            - containerPort: 8080
          envFrom:
            - secretRef:
                name: app-secrets
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            periodSeconds: 10
Enter fullscreen mode Exit fullscreen mode

Kustomize overlays handle the per-environment differences:

# k8s/overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production
resources:
  - ../../base
images:
  - name: iad.ocir.io/mytenancy/myapp
    newTag: v1.2.3
Enter fullscreen mode Exit fullscreen mode

The Alignment Check I Actually Use

# Makefile
dev:
    docker compose up --build

build:
    docker build -t iad.ocir.io/$(TENANCY)/myapp:$(TAG) .
    docker push iad.ocir.io/$(TENANCY)/myapp:$(TAG)

deploy-staging:
    kubectl apply -k k8s/overlays/staging

check-alignment:
    @echo "=== Compose ports ===" && grep -A1 "ports:" docker-compose.yml
    @echo "=== K8s ports ===" && grep "containerPort" k8s/base/deployment.yaml
    @echo "=== Compose health ===" && grep "test:" docker-compose.yml
    @echo "=== K8s health ===" && grep "path:" k8s/base/deployment.yaml
Enter fullscreen mode Exit fullscreen mode

make check-alignment is dumb but it catches drift. I run it before every deploy. It's saved me twice already from deploying with mismatched health check paths.

The Honest Take

This isn't elegant. I'd love a single config file that works everywhere. But every tool I tried to achieve that added more complexity than it removed.

The current setup is boring and it works. Compose for local, Kustomize for OKE, same Docker image, same env var names, a Makefile to keep me honest. I understand every piece of it, and when something breaks at 2am, that matters more than elegance.


Pavan Madduri — Oracle ACE Associate, CNCF Golden Kubestronaut. GitHub | LinkedIn | Website | Google Scholar | ResearchGate

Top comments (0)