Cloud Application Development

Cloud application development at Software Mile builds software designed for the cloud from the start – not a desktop app dragged onto a server. That means applications that scale with demand, cost what they should, and stay up when a single machine would not.

Cloud-Native, Deliberately

We build on AWS, Azure, and Google Cloud using the platform’s own services rather than reinventing them – managed databases, queues, storage, and identity – so you get resilience and scale without operating everything yourself. Architecture decisions are made with your cost model in view, because cloud that scales your bill faster than your business is a failure mode we design against.

  • Cloud-native applications and APIs built for elasticity and resilience
  • Serverless and containerized architectures matched to your workload and budget
  • Managed-service integration: databases, queues, storage, auth, and monitoring
  • CI/CD pipelines so deployment is routine, not an event

Moving an existing system rather than building new? See cloud migration. Tell us your target platform and workload and we will architect for it.

Serverless and Containers Suit Different Traffic Shapes

The answer depends on the shape of your traffic, not on which option is more modern. Workloads that sit idle most of the day and spike unpredictably tend to cost less and take less operating effort on serverless, because idle time is not billed. Steady, high-utilization workloads usually come out ahead on containers once request volume is constant.

A few factors push the decision hard. Long-running jobs, large in-memory working sets, unusual native dependencies, and latency budgets where cold starts are unacceptable all argue for containers, and so does a team that already runs them well. Mixing both in one system is normal: containers for the core service, serverless for scheduled jobs, event handling, and glue.

How Deep Should You Go on Managed Services?

Managed services trade operational work for coupling, and the trade is not the same for all of them. A managed relational database running a standard engine is close to portable, and running it yourself buys almost nothing. A proprietary workflow engine or an unusual serverless data store buys real convenience but is genuinely hard to leave.

A workable rule: adopt freely where the service is a standard engine or protocol behind a managed operator, and deliberately where the interface is the vendor’s own. In the second case, keep business logic out of the vendor’s abstraction and behind a thin adapter you control. That is a narrow piece of insulation around one service. It is a much smaller commitment than an abstraction layer built to span two providers, which our cloud migration page argues against for good reasons.

Does Your Application Need Microservices?

For a first version, usually not. Microservices buy independent deployment and independent scaling, and they cost you distributed debugging, network failure modes between things that used to be function calls, data consistency across boundaries, and a platform layer somebody has to run.

A well-structured application with clear internal module boundaries, deployed as one unit on a managed container service, will carry a serious workload and a growing team before its shape becomes the constraint. Split when a specific constraint appears: a component with a different scaling profile, a compliance boundary, or teams blocking each other’s releases. Splitting a clean modular application later is tractable. Splitting a tangled one is a project of its own, which is why internal boundaries deserve attention early.

If You Are Building a Product, How Will You Handle Tenants?

Multi-tenancy is one of the few decisions that is genuinely hard to change after launch, because it reaches the data model, the backup strategy, the migration process, and the price list. Shared tables with a tenant identifier scale to many small customers cheaply. A schema or database per tenant makes isolation, per-customer restore, and data residency straightforward, and makes schema changes slower.

A dedicated stack per tenant is sometimes required by an enterprise buyer, and it changes the cost structure, because the per-customer floor is no longer shared. Decide against the customers you expect to sign rather than the ones you hope for, and write down which of the three you chose and why.

What Is Worth Building In From Day One?

A few things are cheap to include at the start and awkward to retrofit, because retrofitting them means changing code that is already carrying production traffic. They are worth insisting on even in a first release.

Whoever ends up running the system, these are the starting conditions that make it supportable. Whether the running of it needs a retainer at all is a separate question, and our cloud support and maintenance page is blunt that plenty of systems do not.

  • Structured logs and request identifiers that let you follow one transaction end to end
  • Configuration and secrets held outside the code, with different values per environment
  • Idempotent handling anywhere a message or payment can be delivered twice
  • Database schema changes as versioned, reviewable migrations rather than manual edits
  • A deployment that runs the same way in every environment, including a developer’s machine

The Order the Work Runs In

The decisions that are expensive to revisit come first: which provider and which region, how tenants are separated, whether the core runs as containers or as functions, and where state lives. Those come out of a short discovery pass against real numbers, and they get written down with the reasoning attached, because everything built later inherits them and somebody will ask in a year why it is this way.

The first thing deployed is deliberately trivial. One request that enters through the real entry point, touches the real data store, returns a real response, and gets there through the same pipeline that will ship every later change. That slice proves the parts nobody demos, which are identity, networking, permissions, and deployment, at a point where there is almost no application code to confuse the diagnosis. Feature work after that is a series of slices through the same path, rather than layers assembled separately and integrated at the end under time pressure.

  • Discovery: load shape, the systems it has to call, data that cannot leave a jurisdiction, and the constraints that come from regulation rather than preference.
  • Account and environment layout with the identity and permission model, defined as code before anything is created by hand in a console.
  • The thin end-to-end slice, deployed through the pipeline, so the first release happens while it is still small enough to debug.
  • Feature work in vertical slices, each one deployable on its own and each one behind the same automated checks.
  • Load and failure exercises in a non-production environment: peak traffic, a dependency that times out instead of failing outright, an instance that disappears mid-request.
  • Handover: the repository, the pipeline configuration, the infrastructure definitions, account access, and a runbook for whichever alerts exist.

What Has to Exist on Your Side

Schedule damage on a cloud build usually comes from access, not from code. Every item below sits behind somebody outside the project: a DNS change waits on whoever holds the domain, a sandbox tenant waits on an identity administrator, and vendor test credentials wait on the vendor's own process. They are worth starting during discovery, when each one costs an email, rather than at integration time, when they block a team that is otherwise ready to work.

Regulated data adds paperwork with its own lead time. If the application will handle protected health information, the Business Associate Agreement with the cloud provider is executed by whoever owns the cloud account, and it covers only the services that provider publishes as HIPAA-eligible, which is a subset of the catalog and varies by region. Cardholder data has the same outline: the provider attests for the infrastructure, and how the application stores and transmits the data is assessed separately, which is an argument for keeping that data inside as few components as possible.

  • A decision about which organization's cloud account the resources live in and which billing account they roll up to, settled before the first resource exists rather than renegotiated afterward.
  • Control of the DNS zone for the name the application will answer on, or a subdomain delegated to it, since certificate issuance and automated renewal both depend on records there.
  • A test tenant in the identity provider when sign-in is federated, plus an administrator who can register an application and consent to the scopes it requests.
  • For each system the application has to call, a reachable non-production endpoint and credentials for it, or a written decision that a stub stands in until launch.
  • Data that matches production in shape and volume, synthetic where the real thing cannot be used, because a query plan that looks fine over a thousand rows is not evidence about a million.
  • One person who can settle a scope question within a day and one who can approve spend, which are frequently not the same person.

The Bill Has Line Items Nobody Estimates

Compute is the number people estimate, and it is usually the one they get closest to. Overruns come from data movement and from observability, both of which track traffic and retention rather than feature count. Two habits carry most of the benefit: tag every resource with an owner and an environment as it is created, because an untagged account can report the total and nothing more useful than that, and leave commitment-based discounts alone until there are months of real usage to commit against, since a one-year or three-year commitment sized against a guess costs more than paying list price.

The items below are worth pricing while the architecture is still on paper, when moving a component into a different subnet or changing a retention setting is a text edit instead of a migration.

  • Data leaving the provider network. Inbound transfer is generally free and outbound is billed per gigabyte, so an API that returns large payloads to consumers outside the cloud pays that charge on every response.
  • NAT gateways, which carry an hourly charge per gateway plus a per-gigabyte processing charge on everything routed through them, including calls to the provider's own services that a private endpoint could have served instead.
  • Cross-zone traffic between components that were spread across availability zones for resilience and then talk to each other on every single request.
  • Log and metric ingestion, billed on volume ingested and again on how long it is kept. Debug-level logging left on in production is a recurring charge, not a one-time one.
  • Capacity that never scales to zero: load balancers, allocated database instances, reserved concurrency, and non-production environments running through nights and weekends.
  • Accumulation nobody deletes: snapshots, old container images, and object versions retained by a versioning setting that was switched on once and never revisited.

The Failures That Only Appear Under Load

Early production incidents on a new cloud system are usually not defects in feature code. They are limits nobody reached during development, and they share a diagnostic signature: the application logs look clean, because whatever failed happened outside the application's own request handling. That is the tell. When a caller reports an error the service has no record of, examine the layers between them before examining the code.

These are the checks that catch most of it, and every one of them is cheaper before launch than after.

  • Service quotas. Provider limits are set per account and per region, at defaults chosen for typical use rather than for your launch day. Raising one is a request that goes into a queue, so the reading happens weeks ahead of the traffic, not during it.
  • Timeout alignment. If the load balancer's idle timeout is shorter than the application's own, the caller receives a gateway error while the service logs a slow success. Set each layer's timeout slightly longer than the layer it calls, so the innermost one fires first and the error names the real cause.
  • Connection budgets. Function-based compute scales out to hundreds of concurrent executions, while a relational database has a fixed maximum connection count tied to its instance size. Without a pooler in front of the database or a concurrency ceiling on the callers, connections start being refused exactly when traffic is highest.
  • Recovery amplification. When a failed dependency returns, every waiting client retries at the same moment. Exponential backoff with random jitter spreads that return; backoff without jitter only regroups the clients into fewer, larger spikes.
  • Certificate renewal. Automated issuance renews only while its validation method still works, so a DNS validation record removed during an unrelated cleanup breaks a renewal that is not due for months. Alert on days remaining rather than on renewal failure.

When This Is Not the Work to Buy

Some requirements do not justify a build. Where commodity software already covers the process and the gap is configuration rather than function, buying it wins, and the useful engineering question becomes what the integration around it looks like. Where the system is internal, has a bounded and known user count, and has no elasticity requirement at all, a managed host running a single deployable is proportionate: the architecture this page describes starts paying when load varies, when downtime has a cost attached, and when the system has to keep growing after the first release.

The boundary with cloud migration is not whether a system already exists. It is whether its code is coming along. Lifting a running application onto cloud infrastructure keeps the code and changes what it sits on. Replacing that application keeps the data and the business rules and discards the code, which is a new build even though something is running today. Naming which of the two is intended, in writing, prevents a later argument about what finished means, because the two carry different risks and different tests for success.

  • A commodity process such as payroll, ticketing, or expense handling, where the requirement differs from what the market already sells by a configuration setting.
  • A reporting problem, where the real need is a warehouse and a reporting tool over data you already hold, not an application that draws charts on top of it.
  • A single scheduled job. One function on a timer, with logging and an alert, is a complete answer, and wrapping it in an application is the expensive version of the same thing.

Frequently Asked Questions

Which cloud provider should we build on, and who decides that?

You decide, and the deciding facts are usually not technical. An identity directory you already run, an existing agreement or committed spend with one provider, or operations staff who already know one console will outweigh any feature comparison. For standard workloads, meaning a web application, an API, a relational database, a queue, and object storage, the three major platforms are close enough that the differences turn up in operations rather than in capability. It becomes genuinely decisive only where one specific managed service is materially better on one platform, or where a region or residency requirement narrows the field, since not every service exists in every region.

What does the finished application actually run on, and does anything still need attention afterward?

It runs on the provider's managed compute, either a container service or a function runtime, fronted by a managed load balancer or API gateway, with managed data stores behind it. You are not patching operating systems, but versions do not take care of themselves: managed language runtimes and managed database engines have published end-of-support dates, after which the provider stops issuing security patches and eventually applies the upgrade on its own schedule. Plan runtime and engine upgrades as routine work once or twice a year, which turns a deprecation notice into a calendar item instead of an interruption.

How does a cloud application reach systems that stay in our building or data center?

There are three options, in increasing order of setup effort. If the internal system can safely expose an HTTPS endpoint, the application calls it and your firewall allowlists the source, which means the application needs a stable outbound address rather than whatever the platform assigns it. If exposure is not acceptable, a site-to-site IPsec VPN between your network and the cloud network gives private routing over the public internet, and it is a configuration change at both ends rather than a procurement exercise. A dedicated private circuit adds predictable latency and higher bandwidth but is ordered through a carrier, so it carries a lead time worth discovering early. What the internal system is permitted to do usually decides this, not what is technically preferable.

Does the application need to run in more than one region?

Usually not. Spreading across availability zones inside one region is a configuration choice on most managed services, defends against the failure that actually happens, which is one data center having a bad day, and costs little beyond some cross-zone traffic. A second region is a different undertaking: replicated data, a decision about what happens to in-flight writes during failover, an accepted amount of replication lag that equals potential data loss, and failover rehearsals frequent enough to trust the result. Base the choice on a stated recovery time objective and recovery point objective. In practice, data residency rules are a more common reason to run in two places than resilience is.

We already have a development team. How does that change the work?

It changes the split rather than the sequence. The arrangement with the least friction is a single owner for the deployment pipeline and the infrastructure definitions, with feature work divided by component so that two groups are not editing the same module in the same week. Shared code review from the first week does more than any written convention document, because it is the mechanism that keeps two sets of habits from diverging quietly. Settle early which side answers the alerts once it is live, since that changes how much operational tooling is worth building and who the runbook is written for.

What if our requirements are not settled yet?

Then the right first purchase is smaller than a build. A discovery pass, or a prototype scoped to the one or two questions that are genuinely open, such as whether two data sets can be reconciled at all or whether an integration returns what you need, answers those questions for a fraction of what answering them inside a full build costs. The trap is starting construction on the expectation that requirements will firm up along the way, because the structural decisions get made regardless, just implicitly, by whoever writes the first module.