Cloud-Native From Day One Without Building a Platform Too Soon
The deployment worked on a laptop. It worked in CI. Then the first production container started, tried to write an uploaded file to its local filesystem, and lost it when the scheduler moved the workload ten minutes later.
That is usually when someone says the application needs to become cloud native.
No. It needed to stop assuming that one process, one disk, and one machine would live forever.
Cloud-native from day one does not mean starting with Kubernetes, six microservices, and a platform team. It means making a few architectural decisions that remain useful when the product has more traffic, more engineers, and less tolerance for downtime. The goal is not to predict every future requirement. It is to avoid choices that make ordinary growth feel like a modernization programme.
Start with replaceable processes
A process should be disposable. If terminating one instance causes data loss, corrupts a job, or signs every user out, the application is carrying infrastructure assumptions in its code.
Persistent data belongs in a database, object store, or another service designed to retain it. Session state belongs in a shared store or, where appropriate, in signed tokens with carefully controlled lifetimes. Configuration comes from the environment or a mounted secret, not a file edited inside an image.
A basic container should also behave properly when the runtime asks it to stop. PID 1 must receive signals, and the application must finish or abandon work deliberately. This Dockerfile pattern is boring, which is exactly what we want:
FROM node:22-alpine AS build
WORKDIR /src
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /src/dist ./dist
USER node
CMD ["node", "dist/server.js"]
Use the exec form of CMD. Listen for SIGTERM. Stop accepting new requests, allow bounded in-flight work to finish, then exit. If shutdown can wait forever, it eventually will.
None of this requires Kubernetes. It works just as well on ECS, Cloud Run, Azure Container Apps, Nomad, or a plain container runtime. That portability is a useful side effect, not the main objective.
Draw boundaries before splitting services
A modular monolith is my default starting point for most product software development. One deployment gives a small team fewer moving parts, while explicit module boundaries prevent the codebase from becoming one large dependency cycle.
Keep identity, billing, catalogue, notifications, or whatever domains the product actually has behind interfaces. Do not let every module query every table. Ownership should be visible in code and migrations, even if the modules share one database initially.
This gives you a cheap way to learn where the real boundaries are. Later, if one module needs independent scaling, stricter isolation, or a different release cadence, extraction is possible. Splitting early based on nouns in a workshop usually produces distributed coupling rather than independent services.
The test is simple: can a developer change a module without reading half the repository? If not, adding a network call will not fix the design.
Treat API design as a compatibility promise
An API is not just a controller wired to a database model. Once another application, customer, workflow, or internal tool uses it, its behaviour becomes a contract.
Use stable resource names and explicit request schemas. Reject unknown or invalid input rather than quietly guessing. Return machine-readable errors with a consistent shape. For write operations that callers may retry, support idempotency.
POST /v1/orders HTTP/1.1
Content-Type: application/json
Idempotency-Key: 01J9Y8M4R8JX2QW5K3V7N6P1TZ
{
"customer_id": "cus_4821",
"items": [{"sku": "rack-bolt-m8", "quantity": 20}]
}
Store the key with the result inside the same transactional boundary as the operation. A timeout must not leave the caller wondering whether retrying will create a second order.
Avoid returning persistence objects directly. Database columns change for reasons that should not affect consumers. An explicit response model gives the API room to evolve.
Version only when compatibility genuinely breaks. Adding /v2 for every new field creates archaeology, not architecture. Prefer additive changes, sensible defaults, and deprecation windows you can actually honour.
Make failure a normal code path
Networks time out. Dependencies throttle. Messages arrive twice. A cloud-native application is designed around those facts instead of treating them as exceptional events.
Every outbound call needs a timeout. Retries need a limit, exponential backoff, and jitter. Only retry operations known to be safe. A retry loop around a non-idempotent payment request is an incident waiting for a date.
Background consumers should assume duplicate delivery. Record processed message identifiers where that is practical, or make handlers naturally idempotent. Put repeatedly failing messages somewhere inspectable rather than cycling them forever and filling the logs.
Health endpoints also need distinct meanings. Liveness answers whether the process is stuck and should be restarted. Readiness answers whether it can currently serve traffic. Making liveness depend on the database can turn a database interruption into a full restart storm.
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 8080
periodSeconds: 5
These probes are not substitutes for monitoring. They are controls with side effects, so test their failure behaviour.
Put the operating contract in the repository
If deployment knowledge lives in one engineer's shell history, the system is not ready to grow.
Keep application code, database migrations, deployment definitions, and pipeline checks under review. Build one immutable image and promote that same artifact through environments. Rebuilding for production means production receives something you did not test.
A sensible pipeline runs unit and integration tests, scans dependencies and images, produces a software bill of materials, applies policy checks, and signs or records provenance for the artifact. Tools such as Trivy, Syft, Cosign, Semgrep, and Open Policy Agent can cover parts of this, but tool count is not the measure of maturity. A check that everyone bypasses is decoration.
The application should emit structured logs to standard output, expose useful metrics, and propagate trace context across HTTP calls and queued work. Include request or correlation identifiers. Do not include access tokens, passwords, or full customer payloads. We have cleaned up enough logging accidents to be blunt about this: logs are data stores, even when nobody designed them as one.
Internal tools deserve production boundaries too
Internal tools often begin as a script with broad credentials and a friendly README. Six months later, that script can modify production records and nobody knows which version an operator ran.
Put consequential operations behind authenticated services or controlled workflows. Give them narrow permissions, validation, audit events, and dry-run modes. A useful admin endpoint says what it will change before it changes anything. For dangerous actions, require an explicit target and a reason that lands in the audit trail.
This is product and platform engineering meeting in the middle. The user group may be small, but the blast radius is not.
Do not build the platform before the product
Teams can waste months creating golden paths for workloads that do not exist. Start with a thin paved road: one repository template, one deployment pattern, one observability baseline, and one hardened pipeline. Add abstraction after the second or third real repetition, when the shared shape is visible.
That approach ages better because it preserves options. The application can move between runtimes, modules can become services when evidence supports it, and delivery controls can tighten without redesigning the entire product.
If you only do one thing this week, restart a production-like instance during an active request and a background job. Watch what gets lost, duplicated, or stuck. That small test will tell you more about your cloud-native readiness than another architecture diagram.
If this is on your plate, TecLeads does exactly this as part of our Software Development work. If you'd like a second pair of eyes on your setup, book a 30-minute call or explore what we do.