The problem: lifting a server into the cloud and expecting it to behave
A team moves a Spring Boot service from a single rented server to a cloud account. It runs on one virtual machine, keeps uploaded files on local disk, stores sessions in memory and talks to a database installed on the same machine. After a month the machine is restarted by the provider for maintenance, sessions vanish, the files are gone, and the bill contains items nobody understood. The cloud did what it promises: resources are rented, can be replaced at any time, and are charged by usage. The architecture did not adapt.
This article gives the vocabulary and the design habits that make a service behave well on any major provider. Names differ between AWS, Azure and Google Cloud, but the categories are the same.
Regions and availability zones
A region is a geographic area with several independent data centre groups. An availability zone is one such isolated group inside a region, with its own power and networking. Zones in one region are connected with low-latency links.
- Running instances in at least two zones protects you from the failure of one zone. It does not protect you from a region-wide problem or from your own bad deploy.
- Choose a region by where your users are, by data-residency and legal requirements, and by which services you need. Latency between regions is noticeably higher than between zones, and moving data across them is usually charged.
- Multi-region designs add data replication, conflict handling and operational complexity. Start with multi-zone and move to multi-region only when a requirement (an availability target or a regulation) justifies it.
Stateless services
A stateless service keeps no request-related data in its own process or disk between requests. Anything that must survive goes to a shared store.
┌──────────────┐
users -> │ load balancer│
└──────┬───────┘
┌────────┼────────┐
[app] [app] [app] <- replaceable, in several zones
└────┬───────┬────┘
[database] [object storage] [cache]If any app instance can be killed and replaced without losing anything, you can scale horizontally, deploy with rolling updates and survive node failures. Practical consequences: sessions or tokens in a shared store (or stateless tokens), uploaded files in object storage, configuration from environment, scheduled jobs designed so that two instances do not run the same job at once, and logs shipped out of the instance rather than kept on its disk.
Managed services versus self-hosted
For each building block (database, cache, message broker, search) you choose between running it yourself on virtual machines or containers and using the provider's managed offering.
| Aspect | Managed | Self-hosted |
|---|---|---|
| Patching, backups, failover | Provider does most of it | Your team owns it |
| Control over versions and tuning | Limited | Full |
| Portability | Often tied to provider features | Higher |
| Operational skills needed | Fewer | Real depth required |
| Cost shape | Pay for convenience | Pay in engineer time and risk |
Managed services usually win for small and mid-sized teams because the hard part of running a database is not installing it but operating it through failures and upgrades. Self-hosting makes sense when you need a specific version or extension, strict control, or the managed offering does not fit. Keep the interface standard (PostgreSQL protocol, Kafka protocol, S3-style object API) where you can, so a change of provider is a project and not a rewrite.
Choosing a database
Start from access patterns, not from fashion.
- A relational database is the default for transactional business data: constraints, joins and transactions are valuable. Managed PostgreSQL covers a very wide range of needs.
- A key-value or document store fits data accessed by key with flexible shape and very high write rates, at the cost of weaker consistency across records.
- A cache (in-memory store) speeds reads but is not a source of truth.
- Object storage is for files and large blobs; it is cheap and durable, but not a file system with fast random updates.
- A search engine is for full-text and faceted queries, usually fed from the primary database.
Adding several data stores multiplies the operational load and the consistency problems between them. Do it when a measured need exists.
Cost drivers
Prices change and differ per provider, so do not memorise them; learn what drives the bill.
- Compute time of running instances, including idle ones and over-sized ones.
- Storage volume and its type, and especially snapshots and logs that are never deleted.
- Data transfer, particularly traffic leaving the provider and traffic between zones or regions.
- Managed service tiers, including requests, operations or provisioned capacity.
- Observability data: logs and metrics can become a large item if ingested without limits.
- Forgotten resources: test environments, unattached disks, old images.
Practical controls: tag every resource with a team and environment, set budgets and alerts, switch off non-production environments outside working hours, set retention on logs and snapshots, and review the bill regularly with the people who make design decisions. Autoscaling helps but can also scale a bug into a large bill, so set upper limits.
Shared-responsibility model
The provider is responsible for the security of the cloud: physical facilities, hardware and the virtualisation layer. You are responsible for security in the cloud: identity and access management, network rules, data classification and encryption choices, patching of anything you run, and your application code. The split moves with the service type: with virtual machines you patch the operating system; with a managed database the provider patches the engine, but you still own users, permissions, network exposure and backups policy.
The mistakes that appear most in incidents are on the customer side: a storage bucket made public, an over-privileged role, a database reachable from the internet, a leaked access key. Use least-privilege identities for each service, keep databases in private networks, and manage secrets in a dedicated secret store.
Common mistakes
- Treating a cloud VM like a pet server with manual changes. Describe infrastructure as code so environments can be recreated.
- One giant, shared account or subscription for everything. Separate environments and ownership boundaries.
- Using the highest-availability setup everywhere. Match the design to the actual requirement.
- Ignoring egress and cross-zone traffic cost in chatty service designs.
- No tested backup restore. A backup you have never restored is a hope.
- Designing for provider-specific features without knowing the exit cost.
Trade-offs
Managed services reduce toil but increase dependency on one vendor. Multi-cloud avoids lock-in but doubles the skills and tooling you need, and most teams gain more from a clean design on one provider with standard interfaces. Serverless functions remove server management and scale to zero, but have cold starts, execution limits and different debugging; they suit event-driven and spiky workloads more than long-running stateful ones. Kubernetes gives a portable platform, at the price of learning and operating it.
Checklist
- At least two zones for anything that must stay available.
- Stateless application instances; files in object storage.
- Managed services unless you have a reason to self-host.
- Databases in private networks, with tested backups.
- Least-privilege identities and secrets in a secret store.
- Tags, budgets and retention policies set.
Where to go next
- Package your service for any platform in Docker for Java services.
- Plan for the failures the cloud will cause in Failure handling in distributed systems.
- Practise reasoning about these choices in the solution architect roadmap.