Cloud Services

Cloud services from Software Mile cover the work around the application: the architecture, infrastructure, and DevOps practices that decide whether your cloud footprint is an asset or a recurring headache. We design and build cloud environments that are secure, observable, and cost-aware.

What We Provide

  • Cloud architecture on AWS, Azure, and Google Cloud – designed for your workload, not a generic template
  • Infrastructure as code, so your environment is reproducible and reviewable, not clicked together by hand
  • CI/CD pipelines and DevOps practice that make deployment safe and routine
  • Monitoring, logging, and alerting so you find problems before your users do
  • Cost analysis and optimization – because the biggest cloud bills are usually the most fixable

Where Security Fits

Cloud environments live and die on configuration. We build with security defaults – least-privilege identity, encrypted storage, network segmentation – and for deeper cloud security engineering, our sister practice BulletproofSoft takes it further.

Tell us your platform and what you are running – we will design or improve the environment around it.

Do You Need a Platform, or Just a Working Environment?

There is a real difference between cloud infrastructure that supports one application and a platform layer built for many teams. If you run a handful of services in one region with one team shipping them, a managed container or app service, infrastructure as code, and a deployment pipeline is a complete answer. Adding an orchestration platform on top buys complexity you then have to staff.

Platform investment starts paying when several teams deploy independently, when environments multiply, or when compliance requires the same controls proven across everything you run. Until then the money is better spent on the application itself.

What Does Infrastructure as Code Actually Buy You?

Four things worth paying for: you can rebuild the environment after a bad day, you can stand up a test environment that matches production’s declared infrastructure, changes go through review like any other code, and you can detect when reality has drifted from the definition. Matching the infrastructure is not the same as matching production’s data volume, traffic shape, or third-party state, and no tooling closes that gap on its own.

The honest costs are also four. The first environment takes longer than clicking through a console. The discipline only holds if nobody makes manual changes afterward, which is a policy question as much as a technical one. Importing an existing hand-built estate is real work and is usually best done incrementally. And the code itself needs maintenance like any other code.

How Do You Know Whether Your Monitoring Is Any Good?

One test: when something breaks, do you find out before a user tells you? If the answer is usually no, more dashboards will not fix it. Alerting has to fire on symptoms users actually feel, such as error rates, latency, failed jobs, queues that stop draining, and orders that stop arriving. Machine metrics may or may not correlate with any of those.

The second test is whether anyone acts. Every alert should have an owner and a documented response. An alert that fires regularly and is always ignored trains a team to dismiss the page that matters. Retention deserves a decision too: logs and metrics are billed by volume, and keeping everything forever has a monthly price.

Where Does Cloud Spend Actually Leak?

Start with attribution, before optimizing anything. Get tagging in place so spend maps to a team, an environment, and a product. You cannot reduce a cost you cannot attribute, and an argument about a cloud bill is often an argument about who owns which line.

Commitment discounts come last. Buying reserved or committed capacity before the workload is right-sized locks in the wrong shape at a discount. Right-size, remove what is unused, then commit to what remains, so the discount applies to the workload you actually have.

  • Non-production environments running around the clock for a team that works office hours
  • Volumes, snapshots, and addresses left behind by resources that were deleted
  • Databases and instances sized for a peak that has not happened yet
  • Log, metric, and trace ingestion configured once and never revisited
  • Traffic crossing availability zones or leaving the provider, rarely covered in a design review
  • Storage with no lifecycle rule, holding data nobody has read in years

Who Owns the Environment After Handover?

Worth agreeing before the work starts rather than at the end. Cloud accounts should be in your organization’s name with your billing relationship. You should hold administrative access, a break-glass account that does not depend on any one person, and runbooks for the things that go wrong more than once.

After that, the question is who watches it. Three arrangements are reasonable: you take it in-house, you keep us on cloud support and maintenance, or you add our people to your own team through staff augmentation. An environment only the vendor can log into does not belong on that list. Our default is administrative access in your hands from the first day, and where something narrower is genuinely required, by a managed-service boundary or by your own security policy, it should be written down and agreed rather than arrived at by accident.

What Does an Assessment Need From You?

Design starts with an inventory of what is actually running, and the item that derails a cloud project is rarely the main application. It is the reporting database somebody restores by hand every quarter, the scheduled job on somebody's workstation, the fixed IP address an outside partner allowlisted, the license tied to a hostname. Those surface when someone walks the list of running things, not when someone reads an architecture diagram, which is why the inventory is a working session rather than a form to fill in.

The second input is access, and read-only is enough at this stage: viewer rights across the existing cloud accounts, subscriptions, or projects, plus the billing or cost export. Reading the accounts and the billing export together answers questions no workshop can, because it shows what is deployed and what each part costs rather than what the diagram says should be there. Where nothing is in the cloud yet, the equivalent is a hypervisor inventory and the last twelve months of invoices.

The third input is a person who can decide. Cloud design forces choices that cross team boundaries: which team owns a shared database, whether a legacy integration can finally be retired, how much downtime a cutover is allowed to take. Someone with the authority to answer those in the room, or on a short turnaround, moves the work faster than a full specification does. Four further specifics are worth gathering before the first design session, because they are what most often changes a design after it has been drawn:

  • Recovery expectations stated as numbers, per application rather than for the estate as a whole: how much data loss is tolerable, and how long the system can be down.
  • Runtime, framework, and database versions still in use, including anything past vendor support, since those constrain which managed services and machine images are usable targets at all.
  • The maintenance windows you genuinely have, including any customer or contractual limit on when a system can be offline.
  • Existing hosting, colocation, and support contracts with their notice periods and renewal dates, because those set the outer edge of any migration schedule.

Which Decisions Are Expensive to Reverse?

Some choices cost nothing on day one and a great deal in year two. Address space is the clearest. A virtual network numbered 10.0.0.0/16 because that was the default collides the first time you peer with an acquired company, a partner network, or your own office range. Choosing ranges deliberately out of RFC 1918 space and writing down which range is reserved for what costs almost nothing at the start, and afterward the only remedies are renumbering or another layer of address translation.

The second is region and availability zone layout, which is where a data residency obligation stops being a policy document and becomes a design constraint: moving a stateful workload to another region later is a data migration, not a configuration change. The third is the boundary between production and non-production. Separate accounts, subscriptions, or projects give you a blast radius the provider enforces, rather than one that depends on every policy in a shared account being written correctly forever. Splitting them later means moving resources between accounts, and not every resource type supports being moved.

None of those three are hard to get right when they are decided on purpose; they are expensive only when they are inherited from a default. The same is true of four smaller choices that otherwise get made by whoever creates the first resource, and that are worth settling before anyone does:

  • Which directory is authoritative for human access, and whether cloud roles are granted through groups in that directory rather than to individual named accounts.
  • Where DNS is hosted and who is allowed to change it, including whether the production zone is delegated separately from whatever test environments use.
  • The database engine and version, since a managed service supports a narrower version range than a self-installed server and imposes an upgrade cadence you do not control.
  • A naming and tagging convention enforced when a resource is created rather than applied afterward, since a policy check can reject an untagged resource at creation and retrofitted tags land inconsistently.

Lift and Shift, Replatform, or Rearchitect?

Moving an existing application is really three different jobs, and most real environments need a mix of all three. Lift and shift moves the machine roughly as it is: quickest to execute, and it carries the current operating system, patch state, and sizing along with it, so the first month's bill often compares badly with the hardware it replaced. Replatform keeps the application code but swaps components for managed equivalents, most often the database, the load balancer, and the object store; the work is in the differences, because a managed database engine restricts superuser rights, plugin sets, and file system access that a self-hosted install allowed.

Rearchitect changes the application itself, and it is the only one of the three that can change the shape of the cost rather than its level, because it is the only one that lets capacity follow demand. It is also the only one that requires the application team rather than just the infrastructure team, which is why it is scoped per component instead of per estate. A defensible pattern is to replatform the parts with a managed equivalent, lift and shift the parts nobody is going to touch, and rearchitect only what has a specific reason: a component that cannot scale, one that blocks releases, or one running on something no longer supported.

The predictable failure is the lift and shift that was presented internally as a first step. Once the machines are running and the project is declared finished, the pressure to take the second step disappears, and the estate keeps its old shape and gains a rental bill. If a plan depends on a later phase, name what triggers that phase, who owns it, and what budget it sits in before the first machine moves, because after the move nobody is asking.

What Has to Be True Before a Pipeline Helps?

A pipeline automates the release process you already have. If a release today is a person following a document, automation gives you a faster and more repeatable version of that same process, including the parts of it that were never right. The order that works is environments defined as code first, then build, then test, then deploy, because a pipeline that deploys into an environment nobody can rebuild has automated only the last and easiest step.

Deployment strategy is decided by the application, not by preference. Rolling, blue/green, and canary releases all run two versions of the code at once for some period. A database change that is not backward compatible with the previous version rules all of them out and leaves you with stop and replace. The way around that is expand and contract: add the new column, ship code that writes to both, backfill, and remove the old column in a later release, which turns one risky deployment into three dull ones.

Beyond tooling, a pipeline needs a build that produces the same artifact from the same commit on a clean machine, tests whose failure genuinely stops the release, and secrets that live in a managed secret store rather than in the repository. Each of those is a change in how the team works rather than a product you install. A pipeline missing any one of them still runs; it just automates a release you cannot reproduce, cannot verify, or cannot rotate credentials for.

Frequently Asked Questions

Do we have to standardize on one cloud provider?

No, but running the same workload across two providers is rarely worth what it costs. Portability is real at the container and infrastructure-as-code level and thin at the managed-service level, where identity models, managed databases, queues, and their SDKs all differ, so the second provider duplicates the network model, the identity model, and the on-call knowledge. Using different providers for different workloads is common and carries none of that cost. Data moving between providers is billed as egress and adds a network hop, so a design that splits a chatty path across two clouds pays twice.

Can you work in our existing cloud account, or do we need a new one?

Either works, but check who owns the account before anything is built in it. Accounts opened under a personal email address, or bought through a reseller, need an ownership or billing change that is a provider-side process with its own paperwork and timing, and it is far easier to do before resources exist than after. A new account gives you clean boundaries and current default settings, at the price of moving whatever already runs in the old one.

What does a Kubernetes cluster commit us to after it is running?

A continuous upgrade obligation. Kubernetes publishes minor releases several times a year, and managed offerings support each minor version for a limited window measured in months, so a cluster that is left alone eventually falls off supported versions. Upgrades are not only a control plane action: API versions get removed on a published schedule, which breaks manifests and controllers written against them, and add-ons such as the ingress controller, CNI plugin, and CSI drivers each have their own compatibility matrix. That means a standing owner rather than a one-time setup.

What do we need in place before a pipeline can be built?

Source control that everything genuinely builds from, somewhere to store built artifacts with versions, and a named person who approves a production release. If any part of the current release involves copying files onto a server by hand, that step has to be described exactly before it can be automated, because the pipeline will reproduce what it is told and nothing else. Undocumented steps are usually where the surprises are, and writing them down is often the most useful part of the exercise.

How does a database cutover actually work?

Two ways, and the downtime you can accept picks between them. A dump and restore is simple and its outage lasts as long as the copy plus verification, which for a large database can be many hours. Logical replication lets the new database follow the old one while it is still serving, so the window shrinks to stopping writes, letting the last transactions apply, and repointing the application, though it is not supported for every engine and version pair and does not always carry sequences, large objects, and certain object types. If clients reach the database by name, lower the DNS TTL well ahead of the window, because a record cached with a long TTL is cached somewhere you cannot flush.

Can part of the system stay on-premises?

Yes, and hybrid is a normal end state rather than a failed migration. Connectivity is either an IPsec VPN over the internet or a dedicated circuit from the provider, and DNS has to resolve in both directions, which usually means conditional forwarding between your internal zone and the cloud resolver. The real constraint is latency between an application and its database: an application that issues hundreds of small queries per request turns a 20 ms round trip into seconds of added response time. Keep an application and its primary data store on the same side of the link and split at a boundary that is already asynchronous, such as a queue or a nightly file transfer.

Is there a case where you would tell us not to move?

Yes, and a few are common. Hardware bought recently and still depreciating, a workload with flat around-the-clock utilization and no growth, and an application already scheduled for replacement within the year all argue for leaving things where they are. Per-core licensing is another: some vendors count virtual CPUs differently than the sockets you own, and the license terms rather than the infrastructure can decide the cost. In those cases the useful work is often the environment around the application, such as backups you have actually tested and monitoring that pages someone, not a migration.