Migration Planning - Strategic Planning for Your Cloud Exit
Status: Active
Last Updated: 2026-08-14
Category: Migrations - Phase 1: Planning & Assessment
Prerequisites: None
Time: 2-4 hours
Tags: migration, planning, cost-benefit, risk-assessment, self-hosted, cloud-exit, strategy
Summary
Strategic planning guide for migrating from commercial services to self-hosted infrastructure. Covers the why behind cloud exits, honest cost-benefit analysis, risk assessment frameworks, team readiness evaluation, and defining success criteria before you touch a single production system. This is the lesson that prevents failed migrations.
π― What You'll Learn
By the end of this guide, you'll be able to:
- β Articulate concrete reasons for (and against) self-hosting
- β Build a real cost-benefit model comparing commercial vs self-hosted TCO
- β Score your services by migration risk and business criticality
- β Assess whether your team is actually ready
- β Define measurable success criteria for a migration
- β Produce a migration plan document you can execute against
Table of Contents
- Why Self-Host?
- Why NOT Self-Host?
- Cost-Benefit Analysis
- Service Inventory
- Risk Assessment
- Team Readiness
- Success Criteria
- The Migration Plan Document
- Common Mistakes
- Related Lessons
Why Self-Host?
Be honest with yourself here. "It's cool" is a valid bonus, but it's a terrible primary reason. Good primary reasons look like this:
Cost at Scale
Commercial services price per-seat, per-GB, or per-request. At small scale they're cheap. Past a threshold, self-hosting wins:
| Service | Typical SaaS Cost (10-person team) | Self-Hosted Equivalent |
|---|---|---|
| File storage (Dropbox Team) | $150/mo | Nextcloud on existing NAS: ~$0 marginal |
| Object storage (AWS S3, 5TB) | $115/mo + egress | MinIO on your hardware: ~$0 marginal |
| Git hosting (GitHub Enterprise) | $210/mo | Forgejo: free |
| CI/CD minutes (GitHub Actions) | $40-300/mo | Woodpecker on spare nodes: free |
| Monitoring (Datadog, small fleet) | $200+/mo | Prometheus + Grafana: free |
The pattern: self-hosting converts recurring costs into one-time setup + ongoing maintenance time. Whether that's a win depends entirely on how much your time is worth and how much of it you have.
Data Sovereignty
Your data lives on hardware you control, in a jurisdiction you choose, subject to terms you wrote. This matters when:
- Compliance requires knowing exactly where data resides
- A vendor's ToS change could affect your business
- You want to guarantee data deletion is actually deletion
- Vendor lock-in makes switching costs prohibitive
Feature Control and Integration
Self-hosted tools are usually more hackable. Need a weird webhook? A custom auth bridge? With your own instance, you patch or configure it yourself instead of filing a feature request that dies in a backlog.
Learning
Running your own Forgejo/Harbor/Nextcloud stack teaches you more transferable skills than clicking through a SaaS admin panel ever will. For a team building infrastructure skills, this alone can justify migrations.
Why NOT Self-Host?
Write these down too. Every one of them is a reason someone's migration failed:
You Become the SRE
When GitHub goes down, it's GitHub's problem and they have hundreds of engineers on call. When your Forgejo goes down, it's your pager. Ask directly:
- Who responds at 3 AM?
- What's the acceptable recovery time?
- Do you have monitoring that would even tell you it's down?
Hidden Costs Are Real
| Often-Forgotten Cost | Typical Impact |
|---|---|
| Backup storage + offsite copies | 20-50% on top of primary storage |
| Electricity + cooling (self-hosted metal) | $30-200/mo depending on hardware |
| Hardware refresh cycles | Servers die every 4-6 years |
| Your maintenance time | 2-10 hours/month minimum, per stack |
| Security patching cadence | CVEs don't care about your roadmap |
Downtime Tolerance
If your team loses hours every time the file server reboots, the "savings" evaporate. Commercial services offer SLAs; your basement offers best-effort.
Skill Gaps
Migrating to MinIO without understanding S3 semantics, IAM policies, and eventual consistency is how you lose data. If nobody on the team knows the technology, budget serious learning time first β or don't migrate yet.
Rule of thumb: self-host things where downtime is annoying but survivable (git hosting, file sync, CI). Buy managed services for things where downtime is existential (payment processing, customer-facing databases β unless you genuinely have ops capability).
Cost-Benefit Analysis
Build the actual numbers. Here's a template you can fill in for each service.
Total Cost of Ownership Worksheet
=== COMMERCIAL SERVICE (current state) ===
Monthly subscription: $______
Per-user overages: $______
Egress/API fees: $______
Annual total: $______
=== SELF-HOSTED (proposed state) ===
Hardware amortized monthly: $______ (cost / 60 months)
Storage growth provision: $______ (data Γ 1.5 Γ drive cost)
Backup storage (offsite): $______
Electricity estimate: $______ (~10W continuous = ~$1.30/mo)
Migration one-time effort: ____ hours Γ $____/hr
Ongoing maintenance: ___ hrs/mo Γ $___/hr Γ ____
=== BREAK-EVEN ===
One-time cost / monthly savings = ______ months to break even
Worked Example: Migrating 2TB from Dropbox to Nextcloud
COMMERCIAL:
- Dropbox Advanced (3 users): $180/mo β $2,160/yr
SELF-HOSTED:
- NAS + drives (amortized): $15/mo
- Offsite backup (Hetzner SB box): $5/mo
- Electricity: $3/mo
- Maintenance (3 hrs/mo @ $60): $180/mo β THE REAL COST
- One-time migration: 12 hrs @ $60 = $720
Break-even vs subscription savings ($15/mo cash):
Never on pure cash if you value labor.
Break-even vs subscription savings if labor is "free" (learning investment):
~48 months... but skills gained compound.
This example shows why you must decide what your maintenance time is worth before you build the spreadsheet, not after. If the answer is "$0, this is my hobby/learning," self-host everything. If it's "$120/hr billable," self-host selectively.
Non-Financial Benefits Ledger
Track these separately β they don't fit spreadsheets well but matter:
- Data sovereignty requirements met/unmet
- Skills acquired by team members
- Independence from vendor pricing changes
- Ability to integrate deeply with internal tooling
Service Inventory
Before planning anything, enumerate everything. Most teams discover 30% more dependencies than they thought they had.
Inventory Template
| # | Service | Provider | Data Size | Users | Criticality | Integrations | Migration Candidate? |
|---|------------------|-------------|-----------|-------|-------------|-------------------------------|----------------------|
| 1 | Git repos | GitHub | 8 GB | 12 | HIGH | Actions, Slack, webhooks, OIDC| Yes (Forgejo) |
| 2 | Container images | Docker Hub | 45 GB | 6 | MEDIUM | CI pipelines | Yes (Harbor) |
| 3 | File sync | Dropbox | 800 GB | 12 | HIGH | Shared links (public!) | Yes (Nextcloud) |
| 4 | Object storage | AWS S3 | 2 TB | apps | CRITICAL | 3 production apps | Later (MinIO) |
| 5 | Email | Google | 400 GB | 12 | CRITICAL | Everyone's phone, clients | NO (for now) |
| 6 | Issue tracking | Linear | tiny | 12 | MEDIUM | GitHub, Slack | No β keep |
Finding Hidden Dependencies
For each service, answer:
- What talks TO it? Webhooks, API integrations, OAuth logins ("Sign in with GitHub"), CI triggers.
- What does it talk TO? Deploy keys, outbound webhooks, notifications.
- Who has credentials? API keys in laptops'
.envfiles, CI secrets, password manager entries. - What breaks silently? A cron job on someone's laptop syncing to Dropbox. A partner's webhook pointing at your old domain.
Discovery commands worth running across your infra:
# Find references to a service hostname in config files
grep -rn "github.com" /etc /opt /home --include="*.{yml,yaml,toml,conf,env}" \
2>/dev/null | grep -v ".git/"
# List all systemd timers (hidden cron-style jobs)
systemctl list-timers --all
# Find OAuth app grants in GitHub (each one needs recreation after migration)
gh api user/installations --paginate
Output artifact: a completed inventory spreadsheet. Every later phase references it.
Risk Assessment
Score every service on two axes: criticality (how bad if it's down/data lost?) and migration complexity (how likely is the migration itself to break it?).
Risk Scoring Matrix
LOW COMPLEXITY HIGH COMPLEXITY
ββββββββββββββββββββββββ¬βββββββββββββββββββββββ
HIGH β Migrate first β Migrate last, β
CRITICALITY β (with rollback) β carefully, phased β
ββββββββββββββββββββββββΌβββββββββββββββββββββββ€
LOW β Migrate anytime β Consider NOT β
CRITICALITY β (good practice run) β migrating β
ββββββββββββββββββββββββ΄βββββββββββββββββββββββ
Per-Service Risk Questions
Answer these in writing for each candidate service:
- Data loss scenarios: What happens if the transfer corrupts 1% of files? Would we notice? When?
- Downtime impact: What does 4 hours of unavailability cost us β money, trust, deadlines?
- Compliance: Any regulatory requirement about where data lives, how it's encrypted, or how long we keep logs?
- Skill gaps: Can everyone who operates the commercial service operate the replacement? Who needs training?
- Integration blast radius: If this service breaks, which other systems fall over?
Concrete Example: Git Hosting
Service: GitHub β Forgejo
Criticality: HIGH (all source code, CI gates deploys)
Complexity: MEDIUM (git itself is easy; ActionsβWoodpecker, webhooks, OIDC are the work)
Data loss scenarios:
- Unpushed local branches (mitigate: audit clones before freeze)
- Releases/artifacts not covered by git push (mitigate: download all release assets)
- Issues/PR metadata loss in conversion (mitigate: verify counts post-import)
- LFS objects missed (mitigate: inventory LFS usage FIRST)
Downtime impact:
- Acceptable: 4h window on Saturday
- Not acceptable: broken CI on Monday morning
Compliance: none binding, but keep signed commit verification working.
Skill gaps: 2 of 5 engineers never used Woodpecker β schedule walkthrough.
Team Readiness
An honest checklist. If you score under ~70%, extend your timeline or reduce scope β do not lower the bar.
Readiness Checklist
- Two people understand the target technology (bus factor β₯ 2)
- Someone has stood up the target service in a test environment
- Backups exist and have been actually restored at least once
- There is a monitoring story for the new service (see
kb/observability/) - There is a documented rollback plan (see rollback-strategies)
- Maintenance windows are agreed with stakeholders/users
- Users have been told what's changing and what they must do
- Time is budgeted for 48 hours of heightened attention after cutover
- A decision-maker has explicitly accepted the risks above
The Staging Rule
No migration plan is complete until the same steps have succeeded in staging against a copy of production data. "It worked on an empty test instance" proves nothing about the 400 GB of files with Unicode filenames, symlinks, and 2011-era files with weird permissions.
Success Criteria
Define these BEFORE migrating. Vague goals produce migrations that technically finish but satisfy nobody.
SMART Criteria Examples
Instead of: "Move to Forgejo"
Write:
Success criteria β GitHub β Forgejo migration:
1. All 47 repositories cloned via new remotes with identical HEAD SHAs
(verify: git rev-parse HEAD matches on both hosts during parallel run)
2. All release assets downloadable from Forgejo (spot-check oldest 5 releases)
3. Open issues and PRs present with comments intact (count match Β±0)
4. Woodpecker pipeline passes green on 3 flagship repos within 24h of cutover
5. Zero P0/P1 incidents attributable to migration in the following 7 days
6. Old GitHub org set to read-only, not deleted, for 90 days
7. Developers' average clone/push latency < 500ms (subjective survey OK)
Rollback trigger: any of (1), (3), (4) failing 24h post-cutover.
Notice criterion 6: don't delete anything immediately. Decommissioning is a separate, later step (see decommissioning-commercial).
The Migration Plan Document
Consolidate everything into a single document your future self (or a teammate at 2 AM) can execute. Minimum sections:
# Migration Plan: <Service> β <Target>
## 1. Objective & Scope
What moves, what stays, what explicitly does NOT move.
## 2. Current State
Inventory entry reference, data size, integrations, credentials locations.
## 3. Target Architecture
Where it runs, how it's backed up, how it's monitored.
## 4. Timeline
Freeze date β migration window β validation period β decommission date.
## 5. Step-by-step Runbook
Numbered commands with expected output. Written so it can be executed
without improvisation. Include screenshots where UIs are involved.
## 6. Validation Checklist
Concrete checks with expected results (from Success Criteria).
## 7. Rollback Plan
Trigger conditions, exact steps back, owner of the go/no-go call.
## 8. Communication Plan
Who is told what, when. Include "migration done" and "migration rolled back"
message drafts written IN ADVANCE.
## 9. Post-Migration Tasks
Monitoring review at 24h/72h/7d, documentation updates, decommission ticket.
Store the plan in version control next to the infrastructure code. A migration plan that lives in someone's notes app doesn't survive contact with reality.
Common Mistakes
Mistake 1: Migrating the most critical system first
Enthusiasm focuses on the big one (email! everything!). Start with something low-risk and reversible β container registries, a wiki β to calibrate your process. See the phase ordering in the course README.
Mistake 2: No baseline metrics
If you didn't measure current performance (sync speed, CI duration, page load), you cannot prove the migration was neutral-or-better. Capture baselines during planning.
Mistake 3: Planning alone
The person writing the plan always forgets a dependency only a teammate knows about ("oh, the mobile app hardcodes the S3 endpoint"). Review the plan with every heavy user of the service.
Mistake 4: Forgetting the exit FROM the target
You're escaping lock-in β don't create new lock-in. Prefer targets with open formats and documented export paths. Forgejo exports plain git + JSON. Nextcloud uses open protocols. MinIO speaks standard S3. That's why they're good targets.
Mistake 5: Treating estimates as promises
Whatever your time estimate is, triple it for planning purposes. See migration-risks.
π Related
- migration-risks - Next: deep-dive into specific failure modes
- rollback-strategies - Design your Plan B before you need it
- multi-service-migration - When several services move together
- post-migration-validation - Proving the migration worked
- TELOS - The philosophy driving fogserv.cloud's self-hosting strategy
Change Log
- 2026-08-14 β Initial version created as part of KB migrations course build-out.