Cloud repatriation for teams under 50 engineers
Cloud repatriation worked at 37signals because the company already employed people to run servers.
37signals' cloud exit, with an operations team of about ten
On the REWORK podcast episode "Leaving the Cloud" in October 2022, 37signals described itself as a company of about 80 people with an operations team of roughly ten. When the last application moved onto its own hardware in June 2023, 37signals' CTO wrote that the company had done it "without changing the size of the operations team at all." The company's cloud exit FAQ that December filled in the division of labor. The engineers who ran HEY and Basecamp in the cloud went on to run them on 37signals' servers. A hosting provider, Deft (now Summit), unpacked, racked and cabled the machines, so the operations team rarely set foot in a data center.
A company with 30 engineers and no operations team gets the servers without those ten people. Keeping the AWS or Azure account healthy already falls to product engineers between features, and servers add hardware, firmware, hypervisor and backup work to that second job. So the first decision is who runs the servers.
Who replaces a failed disk after cutover?
On AWS, nobody on your team does. After cutover, your engineers do, unless a remote hands order covers the physical swap or a managed provider owns the hardware.
AWS handles the hardware layer today without a ticket from you. When it detects irreparable failure of the host under an instance, it schedules that instance for retirement, and an EBS-backed instance lands on new hardware when it starts again. Simplified automatic recovery is on by default for supported instance types, so a failed system status check moves the instance to another host and the guest sees an unplanned reboot. EBS volumes replicate within their Availability Zone, and RDS restores to any point inside a backup retention window of up to 35 days. Each of those jobs changes hands at cutover.
At 37signals, Deft took the physical layer and the operations team took everything above it. Colocation remote hands menus draw the same line: racking, power cycling and swapping modular parts, each on an order your team places.
| Task | On AWS today | Colocation: your engineers plus remote hands | Managed private cloud |
|---|---|---|---|
| A host's disk, power supply or board fails | AWS retires the instance or recovers it onto healthy hardware | Remote hands swap a spare you keep on site; your engineers rebuild the array and restart the VMs on another host | The provider, which owns the hardware |
| Firmware, hypervisor and host OS updates | AWS | Your engineers | The provider |
| Guest OS patches | Your team on EC2; AWS on RDS | Your engineers | The provider, where the contract names it |
| Database backups and restores | RDS automated backups; your team starts a restore | Your engineers build the backup jobs and test restores | The provider, on your ticket |
| Adding a server | An API call | A purchase order and a delivery date, then remote hands rack and cable it | The provider |
| Deploys, schema changes and application alerts | Your team | Your team | Your team |
We have run our cloud on our own hardware since 2015, and Trinsic Technologies, INC. has operated since 2005. When a customer's staff are all developers, database work, backups and restores come to us as tickets. Our engineers design the environment and support it afterward, so a company this size needs no cloud consultant or AWS specialist of its own.
The trade is speed: every adjustment to a managed environment becomes a ticket and a handoff. A team that wants root on every host belongs in colocation. Managed hosting: who does what? lists each task and who owns it.
Colocation plans tend to miss two things. The first is a contract that assumes remote hands handle a failed disk end to end. The technician swaps the drive you left on site, and the array rebuild, the VM restarts and the health checks wait for your engineer. Until the rebuild finishes, the array runs with less redundancy than it was built with. The second is a rack with no spare capacity. On AWS, the fleet absorbs a host failure. In a rack you own, the VMs on a failed host stay down until the part arrives, unless someone bought an idle server or left enough headroom on the other hosts to restart them there.
Sorting a $48,000 monthly bill by account and service
Before pricing a single server, sort three months of the bill by account and service. One Cost Explorer query does it:
aws ce get-cost-and-usage \
--time-period Start=2026-06-01,End=2026-09-01 \
--granularity MONTHLY --metrics UnblendedCost \
--group-by Type=DIMENSION,Key=LINKED_ACCOUNT Type=DIMENSION,Key=SERVICEThe end date is exclusive, so this window covers June through August. The API accepts at most two group-by keys. If the account carries Savings Plans or reservations, switch the metric to AmortizedCost, which spreads upfront fees across the period they cover instead of showing them as a spike on the day they were charged.
Grouping by linked account matters for companies that keep dev and QA in separate accounts, because non-production spend then shows up as whole rows. A single-account company needs its environment tag in place of the account, and Costs beyond your cloud bill has the tag query and the backfill. On Azure, Cost analysis in the portal has Subscriptions, Resource groups and Services views, and each row expands to the next level down. Daily granularity there covers at most three months.
Then rerun the query for the production account alone, daily, grouped by service and purchase type:
aws ce get-cost-and-usage \
--time-period Start=2026-06-01,End=2026-09-01 \
--granularity DAILY --metrics AmortizedCost \
--filter '{"Dimensions":{"Key":"LINKED_ACCOUNT","Values":["111122223333"]}}' \
--group-by Type=DIMENSION,Key=SERVICE Type=DIMENSION,Key=PURCHASE_TYPEA service whose daily cost barely moves across the quarter and still bills On Demand is paying for elasticity it never uses.
Take Wrenfield Scheduling, a hypothetical appointment scheduling SaaS company with a $48,000 monthly AWS bill across three accounts.
| Line, three-month average | Monthly | Group | Where it goes |
|---|---|---|---|
| Dev and QA accounts, all services | $11,000 | Non-production | Phase 1, except services that call DynamoDB on every request |
| Production EC2, EBS and RDS for PostgreSQL | $24,900 | Steady servers and databases | Phase 2, after the commitment check |
| Production DynamoDB and Lambda | $4,700 | Native to AWS | Stays, with every service that calls it on each request |
| Production S3 | $1,900 | Follows its readers | Leaves in the same phase as the servers that read it |
| Production CloudFront, data transfer, CloudWatch and NAT gateway | $5,500 | Shared | Re-sorted after each phase |
Sort by call path as well as by line item. If Wrenfield's steady EC2 services call DynamoDB on every request, moving them puts an internet round trip and an egress charge on each call, so those services stay with DynamoDB. Private and public cloud shows how to list the SDK clients your code imports and sort services by how portable they are.
Check the steady group against commitments before pricing hardware. Flat usage billed On Demand is first a commitment problem, and a Savings Plan or reservation can close that gap with nothing leaving AWS.
Commitments already on the account set the earliest sensible date for phase 2. Three commands list them with their end dates: aws savingsplans describe-savings-plans, aws ec2 describe-reserved-instances and aws rds describe-reserved-db-instances. A Savings Plan commits you to an hourly spend for one or three years. Compute Savings Plans also apply to Lambda and Fargate, so some of that commitment can land on what stays, while an RDS reservation covers RDS and nothing else. The Reserved Instance Marketplace resells EC2 Standard Reserved Instances only, for a 12 percent fee on the upfront price, so RDS and Convertible reservations run to their end dates.
Software written before the cloud lands in the steady group, because it was never built to scale in and out. We built our cloud to run that kind of software, on Linux and Windows. The native group gets the opposite advice from us: an application that depends on managed services it cannot replace should stay where it is, and we say so when a customer asks to bring one.
Where a hyperscaler still wins
Four situations keep production where it is for a company under 50 engineers.
- Nobody will take the operating work, and no provider fits the budget. Hardware, host and backup work would land on product engineers who already carry the AWS or Azure account.
- Users need several regions, or the product has to survive losing one. Our cloud runs in one U.S. data center.
- A platform team already runs the account well and wants root on every host. That describes 37signals, and colocation with its own servers fits such a team better than a managed provider.
- The product runs very high volume AI inference. Petrichor is too small for that load.
Phase order: QA, then production with the storage it reads
For a team this size, a cloud exit strategy orders the phases by risk and by the dates commitments end. Object storage leaves with the servers that read it. The egress credit rules shape how the last phase is requested and seldom change the sequence.
| Phase | What goes | Starts when | Egress |
|---|---|---|---|
| 1 | Dev and QA accounts or subscriptions | Someone is named for each row of the ownership table | Dataset copies at list price, since production still runs on the same services and none of them empties |
| 2 | Steady production services, with their databases and the object storage they read | A cutover rehearsal has passed, and the Savings Plan or reservation covering those services has ended | Credit only for a service emptied completely, with approval before the first byte leaves |
| Stays | Services native to the hyperscaler and everything that calls them per request | Not scheduled | None |
We start customers on dev or QA, usually QA, because it is the least developed stack and the team can pull back at low risk. Production stays where it is, often on AWS, while phase 1 runs. Phase 1 also tests the ownership table on live requests, since every restore and every patch now goes through whoever you named for it. When to move QA off AWS ahead of production covers the network path back to AWS while QA runs elsewhere.
Phase 2 follows the method we use for cloud migration: design the target environment, present it, then test the cutover repeatedly with your own developers before the production date. A customer who offers us production on the first day gets slowed down until we know the application. Application migration rehearsal covers the rehearsal itself.
For a PostgreSQL database on RDS, the rehearsal starts with logical replication. It needs the static parameter rds.logical_replication set to 1, and the change takes effect only after a reboot, so schedule that restart before the first rehearsal. Logical replication copies rows and leaves schema changes, sequence values and large objects on the source, so freeze migrations during the window and have the cutover script set every sequence to its current value before writes move. Drop the replication slot at the end of each rehearsal: a slot that nobody reads keeps accumulating data on the source until the instance runs out of storage.
Keep the bucket on AWS in phase 2 and every object read becomes internet egress. At 3 TB of reads a month, that is 3,072 GB × $0.09 = $276.48 every month, with each read crossing the public internet. That figure counts from the first gigabyte, on the assumption that production traffic already uses the free 100 GB. Moving storage with its readers makes phase 2 larger and its cutover longer, and it removes that recurring line along with the internet latency on every read.
The copy itself needs a head start. At a sustained 800 Mbps, 20 TB takes more than two days, so the bulk copy runs well ahead of the rehearsal and the cutover window covers only a final incremental sync.
The store that replaces S3 needs an owner in the ownership table and a maintained project. The MinIO community repository, still the default suggestion in many repatriation guides, was archived on April 25, 2026, and its community edition now ships as source code only. Before building on any S3-compatible store, check its own repository for recent releases and for who publishes the binaries.
Egress credit rules on AWS and Azure
AWS waives egress when a customer moves all data off AWS, or all data off one AWS service. The EC2 FAQ sets the order: tell your account team first if you have a negotiated commitment, request the credit through AWS Support, and wait for approval before any data leaves. AWS bases the credit on everything stored across its services at the time of its calculation. After approval you have 90 days, and by the end you delete all remaining data and workloads from the services you vacated, or close the account. Egress through CloudFront, Direct Connect, Snow Family and Global Accelerator is excluded.
A partial exit rarely empties S3. CloudTrail delivers its log files to an S3 bucket, load balancer access logs land in S3, and Terraform's S3 backend keeps state in a bucket. A company that keeps anything on AWS usually keeps those buckets, so S3 never qualifies and its data leaves at list price. EC2 carries the same catch for Wrenfield: if any instance stays beside DynamoDB, EC2 is not vacated either, and RDS becomes the one service phase 2 can empty.
To move off Azure with the exit credit, file a support request with a planned start date and a data estimate. The credit covers at most 60 days of egress and applies only after every subscription on the account is canceled. It also excludes ExpressRoute, VPN, Front Door and CDN egress, so data that leaves over the site-to-site VPN built for phase 1 bills at list. Customers with a UK billing address leaving UK datacenters can exit single services, and everyone else leaving Azure in stages pays list: 6,144 GB at $0.087 per GB is $534.53, once the free 100 GB is spent.
At a small company's volumes, list egress costs little. Moving 6 TB off AWS in US East costs 6,144 GB × $0.09 = $552.96. For 20 TB, the first 10,240 GB bill at $0.09 and the next 10,240 GB at $0.085, for $1,792.00, both counted from the first gigabyte. Wrenfield's phase 2 group bills $24,900 a month, so one extra month of running both environments costs about 14 times the 20 TB transfer.
Waiting for a commitment to end keeps production on the hyperscaler for months after QA has left, and that is usually the cheaper order. Once an account stores hundreds of terabytes, transfer pricing starts to shape the sequence, and AWS egress fees for a 10 TB month prices each line.
Book a call with a consultant and bring the workload you would move first. If you are better off where you are, they will tell you that.