Blog
The Works On My Machine Excuse Ends Here
“Works on my machine” is not explanation. It is confession.
It means your delivery unit is incomplete. You shipped code but not environment. You tested behavior but not runtime assumptions. Then you blame ops when production refuses your fantasy.
Environment Is Part Of Software
Backend code does not run in your IDE. It runs inside OS image, JVM version, timezone, CPU limits, memory limits, network policy, secrets provider, database version, and deployment pipeline.
If your app depends on these things, your engineering must describe these things.
Containerize The Boring Part
A basic Spring Boot Dockerfile is not magic. It is minimum hygiene.
FROM eclipse-temurin:25-jre-alpine
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
USER app
COPY target/payment-service.jar app.jar
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75"
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
This does not solve architecture. It solves one stupid class of excuses: wrong JVM, missing file, different path, random local config.
Your App Must Fail Loudly
Production should not discover missing environment variables after first customer request.
@ConfigurationProperties(prefix = "ledger")
record LedgerProperties(URI endpoint, Duration timeout) {
LedgerProperties {
Objects.requireNonNull(endpoint, "ledger.endpoint");
if (timeout == null || timeout.isNegative() || timeout.isZero()) {
throw new IllegalArgumentException("ledger.timeout must be positive");
}
}
}
Fail at startup. Not after money is half-processed.
Parity Beats Hero Debugging
Use same JVM major version locally and in CI. Run integration tests against containerized dependencies. Keep database migrations deterministic. Store configuration as versioned templates, not tribal memory.
A senior engineer does not say “but ops changed something” and walk away. A senior engineer asks: why was our contract with runtime not explicit?
Containers Are Not Deployment Strategy
Docker is packaging. Kubernetes is scheduling. Neither saves bad app.
If your service logs nothing useful, ignores SIGTERM, stores local state in container filesystem, and needs manual click to initialize schema, container only makes failure portable.
Production Ownership
You own code until it serves real traffic safely. That includes startup, shutdown, metrics, health checks, config validation, migrations, and rollback behavior.
The job is not to make app pass on laptop. The job is to make app boring in production.
Simple Checklist
Pin runtime version. Build immutable image. Run tests in CI. Validate config. Expose health endpoint. Log request correlation id. Document required ports and dependencies. Practice rollback.
Now when something breaks, at least you debug real problem, not snowflake laptop fairy tale.