Hosting Decisions That Are Hard to Reverse
A hosting architecture choice is usually reversible in a weekend. Four parts of it are not: the proprietary runtime, the managed data store, the edge-specific API, and a build only one platform can run.
On This Page

Every hosting platform is easy to leave until you start using the part that made it worth paying for. That is not an accusation about vendors. It is arithmetic. The features that save your team work are the features nobody else implements the same way, and the saving is the coupling.
So the useful question is not whether a platform locks you in. Almost all of them do, a little. The useful question is which of today’s decisions could be undone over a weekend and which would take a quarter with a freeze on everything else. A hosting architecture choice is mostly made of the first kind. Four parts of it are the second kind, and teams routinely agonise over the cheap decisions while signing the expensive ones without reading them.
The four are a proprietary runtime, a managed data store, edge-specific APIs, and a build that only one platform knows how to run. Each is defensible on its own terms. Each is also the reason a web application that took four months to build takes three times that to move. What follows is how to recognise each coupling before you are inside it, what unwinding one actually involves, and the questions worth asking before a build commits to any of them.
Most of it is reversible, and that is the problem
Start with what is not a trap, because the worry is usually misallocated. A large part of hosting is commodity, and moving any of it is a scheduled piece of work with a shape you can describe in advance.
- Region, instance size and scaling rules. These are settings. Changing them is a maintenance window and a load test, not a project with a name.
- CDN and DNS. Both are replaceable by anyone who has done it before, and the risk is concentrated in a time-to-live value rather than anywhere in your code.
- Object storage. Buckets move. The work is a copy and a URL rewrite, and both are boring, which is exactly the property you want in a dependency.
- The CI provider. Pipelines are configuration files. Rewriting them is tedious and predictable, and the tedium is the entire cost.
- TLS, firewall rules and rate limits. Policies you re-express somewhere else. Nobody has to change how the application thinks in order to move them.
None of that is free. All of it is priceable, and a priced risk is a managed one. The four couplings below resist pricing, because their cost is not the migration itself; it is the rewrite the migration forces before the migration can start. That distinction should decide how much scrutiny each decision gets, and it rarely does.
Proprietary runtimes: what actually executes your code
A runtime is proprietary when the shape of your entry points exists on exactly one platform. Not the language, which is almost never the issue. The shape: the handler signature, the request and response objects, the lifecycle hooks, the globals that appear without an import, the way background work is declared, the deployment unit itself.
A platform whose functions accept a standard Web request and return a standard response is a mild commitment; that same code runs behind anything that speaks HTTP. A platform whose handler takes a bespoke context object with vendor methods hanging off it is a strong commitment, and the distance between the two is the distance between an afternoon and a rewrite. The useful part is that this is visible on the first line of every file you are about to write, long before anything is in production.
The tell is the import line
Read the imports at the top of your handlers. If they come from a vendor namespace, that is where portability went. The same applies to values the platform injects into the environment that nothing else provides, and to any object you did not construct and cannot construct locally. Two or three of those is a translation layer somebody can write. Fifty of them is an architecture, and architectures do not move.
This is the same reasoning that sits underneath the choice between a native app, a web app, or neither. The question is never the technology written on the label. It is which decisions the technology quietly makes on your behalf, and how many of them you would have to make again somewhere else.
Keep the vendor at the edge of your code
The mitigation is unglamorous and it works: business logic lives in ordinary functions that take ordinary arguments, and the platform handler is a thin translation on top. Pricing, permissions, scheduling rules and content assembly should be testable without a network. When the runtime changes, you rewrite the translation and leave the rest alone.
The honest concession: nobody does this perfectly, and for a marketing site with six routes it is not worth attempting. Portability is insurance, and insurance is priced against the thing it protects. Do it for the three or four call sites that carry money, identity or content, and let the rest couple freely.
Managed data stores: the coupling with gravity
Data is the hardest thing to move because it is the only part of a system that cannot be recreated from source. Code can be rewritten from a specification. State cannot be rewritten from anything. So a data store deserves scrutiny in proportion to how far it sits from something you could run yourself if you had to.
The commitment is not the export format, which is the thing everyone checks. It is the semantics: the query language and its dialect, the transaction guarantees you assumed, the index types you relied on, the functions and triggers somebody wrote inside the database, the row-level rules that turned out to be your authorisation model, and the connection behaviour your application takes for granted. An export gives you rows. None of that comes with them.
The tell is where your rules live
Ask where a business rule is enforced. If the answer is application code, the store is a service and you can change it. If the answer is a policy configured in the vendor’s console, the database has become part of your application, and moving it means re-implementing behaviour rather than copying rows. That is the difference between a weekend of data transfer and a project with a specification of its own. It is also one of the trades we would file under debt worth keeping, provided somebody wrote down that the trade was made and why.
We recommend managed data stores often, so this is not an argument against them. Running a database well is a full job, and most teams cannot staff it without giving up something they care about more. The line we hold is narrower than it sounds: standard engine, vendor operations. A managed instance of an engine that exists independently is a service contract. A proprietary store with its own query language is a design decision, and it should be argued for on capability rather than convenience.
Edge APIs: behaviour that lives in a console
Edge features are the most pleasant things to adopt and the most awkward things to inventory. Geo headers, per-request rewrites, cookie-based routing, key-value storage at the edge, image transformation on the fly, waiting rooms, bot rules. Every one of them replaces code you would otherwise write and maintain, which is precisely why they get used and why nobody writes them down.
The problem is not the feature. It is where the behaviour is recorded. A rewrite rule configured through a web interface is production behaviour with no diff, no review, no author and no test. Six months later, when visitors in one country see a page nobody expected, the investigation starts with a search of the repository that will find nothing at all.
Two habits fix most of it. Express routing and rewrite rules in files that live in the repository, even where the platform would happily hold them for you. And treat edge key-value storage as a cache with an authoritative source elsewhere, never as the only copy of anything. Then the platform is doing work on your behalf rather than holding something you cannot reconstruct. You will discover how well you did the first time something misbehaves at the edge, which is the argument for observability on a site that rarely breaks: instrumentation earns the most on systems that are usually fine.
Build coupling: the one nobody lists
This is the coupling teams discover last and pay for first, because it blocks every other move. A build is coupled when it only succeeds inside one vendor’s pipeline: environment variables injected at build time, preview URLs baked into configuration, secrets available only to the platform’s builder, workspace detection that guesses which package to compile, a caching primitive nothing else provides.
You do not notice, because it works every day. Then somebody tries to run the same build on a plain container and finds that three things were being handed over for free, one of them by a mechanism nobody on the team has read the documentation for. The build is now the first item on the migration plan, and everything else queues behind it.
The clean-machine test
Take a fresh checkout on a machine that has nothing but a language runtime and a package manager. Supply the environment variables from a file you wrote by hand. Run the build. If it produces the same artefact the platform produces, the build is portable and you can stop thinking about it. If it does not, the gap is your inventory. Run that test on a schedule so it stays true, alongside the rest of website maintenance rather than as a one-off exercise somebody remembers fondly.
It is also the clearest test of whether you can hand a codebase to someone who was not there. A build only one platform can run is a build nobody can inherit, and a repository that cannot be built by a new developer on their first morning is not really a repository. It is a set of instructions for a machine you already own.
Score what you are already standing on
Before choosing anything new, inventory what you have. It takes one sitting, a whiteboard and four columns, and it is the only version of this exercise that survives contact with a real system.
| Coupling | What it looks like | Cost to unwind | Cheapest mitigation |
|---|---|---|---|
| Proprietary runtime | Handlers written to a vendor signature; platform-only globals | Every entry point rewritten, then retested | Thin adapter; logic in plain functions |
| Managed data store | Vendor dialect, vendor-only features inside queries | Schema translation, data move, query rewrite | Standard engine, features in application code |
| Edge key-value state | Flags, sessions or counters held only at the edge | State with no authoritative source to move | Treat edge storage strictly as a cache |
| Console-held routing | Rewrites and redirects configured in a web interface | Behaviour with no diff, author or test | Routing rules committed as files |
| Build coupling | Builds only succeed inside the vendor pipeline | Pipeline rebuilt before anything else can move | Clean-machine build test on a schedule |
| Platform identity | Sessions and users issued by the hosting layer | Every session reissued; users notice | Own the session, treat the platform as a provider |
Write the result down the way an audit is written rather than as a list of concerns. Each item gets a severity, an owner and a recommendation, because a severity can be scheduled and a worry cannot. That is the shape a tool such as Prooflin resolves review notes into, and the reason it matters here is mundane: the version of this document that gets acted on is the version somebody can put in a sprint.
Questions to ask before you commit
Ask them in this order. Most shortlists shorten themselves somewhere around question three.
- What executes my code, and is that shape documented anywhere else? If the handler signature comes from a public specification, you are buying operations. If it exists only in one vendor’s documentation, you are buying an architecture.
- Who defines the query language? A standard engine under vendor operations is a service you can replace. A proprietary engine is a dependency with opinions about your schema, and those opinions travel nowhere.
- Can a clean machine build this? If the build needs the platform to hand it something, the platform is part of your source code and should be listed as such.
- What behaviour lives in a console? Write the list. Everything on it is production logic with no history. Then decide, deliberately, which of those items you are content to keep there.
- Who issues sessions? Platform-issued identity is genuinely convenient, and it is the one coupling you cannot unwind quietly, because every logged-in person experiences the migration personally.
- What would the first week of leaving look like? Not the whole migration. The first week. If nobody can describe it in five minutes, that tells you how well the system is understood, which is the more urgent finding.
The moment to ask what leaving costs is during the evaluation, when you have room to negotiate and nobody is annoyed yet. Ask what a full export contains, how long logs are retained, what data transfer out is charged at, and whether any of those answers change on a cheaper plan. Get them in writing, then file them where the next team will actually look.
Rehearse the exit once, then get on with the work
The exercise worth doing is not a migration. It is a rehearsal. Stand the application up somewhere plain, a container on a second provider or a laptop, with a stub in place of every platform primitive the code reaches for. Do not aim for production quality. Aim for a running process and a number: how long it took, and what stopped you first.
That number is the only honest measure of reversibility anyone will give you, and it ages badly. Re-run it once a year, or after any quarter in which the team adopted something new and enjoyed it. The exercise belongs in the same family as the other rehearsals worth running, including preparing for the day you are compromised, where the whole value likewise comes from having done it once before it counted.
It also gives an honest answer to the question that starts most replatforming conversations. A website transformation that begins with "we need to get off this platform" usually has a different problem underneath, and a rehearsal surfaces it in a day. Sometimes the answer is that the platform is fine and the deployment process is the thing everybody hates.
Accept lock-in that buys capability you could not otherwise staff. Refuse lock-in that buys convenience you could have written yourself in a week.
That rule cuts both ways, which is why we like it. A managed store with automatic failover and point-in-time recovery buys capability, and paying for it in portability is a trade. A proprietary key-value API that saves twenty lines buys convenience, and charges a rewrite for it later. The asymmetry matters more than the inventory, because the coupling you wrote down is never the one that hurts you; the one that hurts is the one adopted on a Tuesday because it was right there.
The final check is not technical at all. Hosting should follow what the business sells, because that determines which failures are survivable and which end a quarter. Our parent company has written about that directly in what a company actually sells, and the point carries here without modification. A publisher and a payments product have different definitions of an acceptable bad hour, and that difference, rather than any feature grid, should set how much irreversibility you are willing to hold. If you want a second opinion on a specific stack, tell us what you are running and what you could not survive losing.
Common questions.
What counts as vendor lock-in when choosing a hosting platform?
Lock-in is any dependency whose removal forces you to rewrite code rather than change a setting. Regions, instance sizes, CDNs and DNS are settings. A proprietary function signature, a database dialect, edge storage holding the only copy of some state, and a build that only succeeds inside one pipeline are rewrites. The second group is what deserves scrutiny before you sign anything.
How do I know whether my build is portable?
Run a clean-machine test. Take a fresh checkout onto a machine with only a language runtime and a package manager, supply environment variables from a file you wrote by hand, and run the build. If the artefact matches what the platform produces, the build is portable. If it fails, the reasons are your dependency list. Schedule the test so the answer stays current.
Is using a managed database considered lock-in?
It depends on whether the engine exists independently of the vendor. A managed instance of a widely available engine is an operations contract, and moving it is mostly data transfer and connection changes. A proprietary store with its own query language, its own index types and its own authorisation rules is a design decision, because moving it means re-implementing behaviour rather than copying rows.
Should we avoid edge functions to stay portable?
No. Edge routing and transformation genuinely improve response times and remove code you would otherwise maintain. The discipline is narrower than avoidance: keep rules in files inside the repository rather than in a web console, and treat edge key-value storage as a cache with an authoritative source elsewhere. Then the platform performs work for you without holding anything you cannot reconstruct.
What actually makes a hosting migration expensive?
The rewrite that has to happen before the migration can begin. Copying files, changing DNS and reissuing certificates are predictable tasks. Rewriting every handler to a different runtime shape, translating a schema between engines, reconstructing behaviour that only existed in a console, and rebuilding a pipeline that was doing undocumented work are not predictable, and they are where the time goes.
How often should a team test whether it could leave its hosting provider?
Once a year is enough for a stable system, plus after any quarter in which the team adopted a new platform primitive. The test is a rehearsal rather than a migration: stand the application up somewhere plain with stubs for platform services, and record how long it took and what blocked you. The number ages quickly, which is the reason to repeat it.
Facing this in your
own business?
Tell us where you’re headed — we’ll map the shortest honest route.