Eight years ago, three engineers started Appsmediaz in a flat in Greater Noida. We had no website, no LinkedIn page, and a single rule: we'd only ship things we'd be willing to maintain in five years. That rule has cost us deals. It's also probably the only reason we're still here.
This article is an attempt to write down — for the team that joined us last month, and for anyone running an engineering team somewhere else — what we've actually learned about taking software from a working prototype to something that survives in production. It's the conversation we wish someone had given us in year one.
If you only read the first 90 seconds:
- Production-ready is a verb, not an adjective — it's the practices you sustain, not a checklist you complete.
- Code reviews aren't a quality gate. They're the cheapest way to spread knowledge across a team.
- Performance budgets beat performance goals — concrete numbers settle arguments before they happen.
- Observability before features — you cannot improve software you cannot see.
- Boring deploys are the most underrated competitive advantage in software engineering.
01 //The honest definition
Every engineering team uses the phrase "production-ready". Almost nobody agrees on what it means. We've watched founders ship code that crashes hourly and call it production-ready because the demo worked. We've watched principal engineers refuse to ship code with one missing unit test, calling it not production-ready, while the user-facing performance is two seconds slower than the competitor.
Our working definition, after eight years and 280+ shipped products: software is production-ready when an on-call engineer who didn't write it can debug it at 2 AM and ship a fix by morning.
That's a single sentence, but it has implications. It means logs need to be readable. It means error messages need to point at the cause. It means deploys need to be reversible. It means the system has to fail in ways you've imagined, with the rollback procedure you've practised. And it means somebody besides the author has to understand the code well enough to fix it.
02 //What "production-ready" is not
It's worth being explicit about what production-readiness is not, because we've watched smart teams burn months on the wrong fights.
It's not 100% test coverage.
Coverage measures lines exercised, not bugs found. We've shipped systems at 70% coverage that run for years without incident, and inherited 95% coverage codebases that go down weekly. What matters is what you test — the critical paths, the money paths, the data-loss paths — not the percentage.
It's not zero technical debt.
A codebase without technical debt is a codebase that hasn't shipped. The question isn't whether you have debt. It's whether you know where it is and whether it's getting paid down at the right rate. We keep a "known debts" file in every active repo. It's a list, not a punishment.
It's not "we used the right framework".
Frameworks are about preference and team familiarity. They're rarely about whether software ships well. We've shipped exceptional Django apps and terrible Next.js apps. The discipline matters more than the stack.
03 //Code reviews as muscle memory
The single highest-leverage practice we've kept since 2017 is mandatory code review. Every commit reviewed by a peer. Even hotfixes. Even Friday at 5 PM. Even when the reviewer is more junior than the author.
The reason isn't quality — it's distribution of context. When one engineer leaves a company (and they do; the industry average is 18 months), the company loses everything they knew. When two engineers always reviewed each other's code, the knowledge survives.
The 2-minute rule: if a code review takes more than 2 minutes to leave a meaningful comment on, the change is probably too big. Break it up. We push back on PRs over 400 lines almost reflexively now.
What we look for in a review, in order of priority:
- Does this do what the issue says it should? Half of all bugs we catch in review are misunderstandings of the requirement, not the implementation.
- What happens when this fails? Every external call, every database write, every queue push — what's the failure mode? Is it handled?
- Will the next person understand this in six months? Comments, naming, structure. The author understands their own code today; the question is whether the next engineer will.
- Is there a simpler version? Almost always yes. We don't always change it. But asking is the habit.
Style and formatting are never in the top four. Those should be automated. If a human reviewer is correcting indentation, you've already lost.
04 //Performance budgets, not aspirations
"Make it fast" is an aspiration. "P95 page load under 2 seconds on a Moto G7 over 3G" is a budget. The first ends in endless meetings. The second ends in a code change.
We set performance budgets on every project at the start, before a line of code is written. They become non-negotiable, like API contracts. If a feature would push you over budget, you either fix it or explicitly accept the trade-off — but you never sleepwalk past it.
Our default budgets for new web products:
// next.config.js — actually used in production
const budgets = {
lcp: '2.5s', // largest contentful paint, p75
inp: '200ms', // interaction to next paint
cls: 0.1, // cumulative layout shift
js_initial: '170KB', // gzipped, on home route
images_total: '1MB', // above the fold
requests_initial: 25, // total requests before interactive
};
The Moto G7 reference isn't arbitrary either. It's the median device in most emerging markets where our clients' users actually live. We test on it. Real users don't have a MacBook Pro on fibre.
"Performance isn't a feature you add. It's a constraint you respect. The teams that ship the fastest products are the ones that treat performance like security — non-negotiable from day one."
— A senior engineer, in a code review05 //Observability before features
The biggest lesson we learned the hard way: you cannot improve software you cannot see.
For the first two years, we shipped Android apps with no crash reporting and websites with no error tracking. We thought we were being lean. What we were actually being was blind. We'd hear about issues from clients, sometimes weeks after they started. By then the user had churned.
Today, the first thing we install on every new project — before authentication, before routing, before the actual feature work — is the observability stack:
- Error tracking — Sentry on every client and server, with source maps uploaded on every deploy
- Structured logging — JSON logs from day one, never
console.logshipped to production - Distributed tracing — OpenTelemetry for any service with more than one moving part
- Real-user monitoring — Core Web Vitals captured for every real session, not lab benchmarks
- Uptime & SLO dashboards — visible on a screen in the office, not buried in a tab
The point isn't the specific tools — these change. The point is that observability earns its place in the stack before the feature does, not after.
06 //Deploys should be boring
The single biggest cultural shift in any team we work with is when deploys stop being events. When merging to main and shipping to production becomes so unremarkable that nobody mentions it in standup anymore, you've crossed a meaningful threshold.
What boring deploys require, in roughly this order:
- One-command deploys — usually just
git push. If it's two commands, it'll be three eventually, then it'll be a forgotten step. - Automated CI — tests, linting, type-checks, build verification — all on every PR.
- Staging that mirrors production — same data shapes, same configs, ideally same hardware tier.
- Feature flags — for anything risky, ship behind a flag, roll out to 1% → 10% → 100%.
- One-command rollback — if rolling forward fails, you should be back to the previous known-good in seconds. Practise it.
The teams we admire most ship 10–50 times per day, casually. Not because they need to. Because the marginal cost of each deploy is near zero, so there's no reason to batch.
07 //On-call as a teacher, not a punishment
The first time a teammate was on-call for a production system in 2018, we treated it like a Soviet posting. They were stressed; we felt bad they were stressed; everyone agreed it was unfortunate.
That was wrong. On-call is the single fastest way to learn how a system actually behaves under load — and how badly the team's documentation, alerting, and runbooks are letting it down.
Today we treat on-call as a teaching rotation. New engineers shadow seniors for a quarter, then take primary with the senior on backup, then run primary alone. Every incident produces a post-mortem (blameless, public to the team) and every post-mortem produces at least one action item — usually an improvement to a runbook, a missing alert, or a clearer log message.
The incidents that hurt the most teach the most. The trick is being honest about what hurt.
08 //Eight lessons in eight years
If we had to compress everything above into eight rules a new engineer should internalise, here's what we'd tell them:
- Boring code wins. The clever one-liner you're proud of today is the bug you'll spend three hours debugging in two years. Write the version your colleague can read at 11 PM.
- If you can't ship it on Friday, your deploys aren't ready. A team that's scared of Friday deploys is a team telling on its own deploy pipeline. Fix the pipeline, not the calendar.
- The thing that broke is not the thing that broke. Almost every incident has a deeper cause than the immediate fix. Stay curious for two more layers.
- Comments tell you why, code tells you how. If a comment is restating the code, delete it. If the code is doing something a reader wouldn't expect, write the comment that explains why.
- Estimates are forecasts, not promises. Honest 60% confidence estimates beat sandbagged 99% ones. Clients prefer reality.
- Read the logs. Most production bugs are visible in logs an hour before they cause incidents. Most teams don't look until after.
- Documentation is for the engineer who joins tomorrow. Not for management, not for your future self, not for compliance. Write it for the person who'll inherit this in three months.
- Software that ships beats software that's perfect. The best engineering team in the world delivers nothing if it can't decide to release. Ship the imperfect thing. Improve it next sprint.
09 //What changes, what doesn't
We've shipped on jQuery, Backbone, Angular 1, Angular 2+, React, Vue, Next.js. We've deployed to bare-metal servers, EC2 instances, ECS containers, Kubernetes, and serverless. We've used Subversion, Mercurial, and Git. We've had stand-ups, no-stand-ups, async stand-ups, and stand-ups in a Slack thread.
The frameworks changed. The fundamentals didn't.
What still holds, in 2026 as in 2017: ship small, review everything, instrument before you build, deploy boringly, and make on-call into a learning loop instead of a punishment. None of that is novel. All of it is hard. The teams that make it look easy are the ones who have done it long enough that it feels like muscle memory.
Eight years in, that's the only thing we'd put on a poster.