2025.09.28

why moving a working system is scarier than building it

building starts with an empty machine. migration starts with users, state and one functioning system you are trying very hard not to damage.

building a system on a new server feels clean.

install the runtime, create the service, add the environment variables, point the domain and start it. if something fails, nothing has been lost because nothing was working there before.

migration has the same steps plus consequences.

there is an old machine containing data, sessions, background workers, DNS records and tiny configuration decisions nobody remembers making. the new machine must become correct before the old one stops being responsible.

that overlap is where the fear lives.

the application is not the whole system

copying the repository is easy. a running service also includes:

  • environment variables and secrets;
  • databases and uploaded files;
  • scheduled jobs and timers;
  • systemd units or containers;
  • reverse-proxy configuration;
  • TLS certificates and DNS;
  • session files and API authorization state;
  • firewall rules;
  • monitoring and backups.

if one of these remains only on the old host, the migration is incomplete even when the homepage loads.

inventory before movement

the safest first step is boring: list everything.

which processes are running? which ports are listening? which domains point here? which directories change over time? which jobs run at midnight? which external systems have this IP allowlisted?

the machine itself is often more honest than the deployment notes. inspect the service manager, containers, cron entries, Nginx configuration and open sockets. documentation tells you what should exist. runtime state tells you what currently does.

state decides the cutover

stateless web code can be copied repeatedly. databases, queues and sessions need a boundary.

if both old and new systems accept writes during migration, their state can diverge. a clear cutover usually means one of these:

  1. briefly stop writes, take the final copy, start the new system;
  2. replicate continuously, then promote the new system;
  3. route all writes to one shared state store during transition.

the correct option depends on how much downtime and complexity the system can afford. "copy it quickly and hope" is also an option, but not one i enjoy documenting.

verification should follow user journeys

a green health endpoint is only the beginning.

can a user sign in? can a bot receive and send a message? does a scheduled job execute? does a file upload survive? are callbacks reaching the new address? are logs and backups working?

then inspect the old host. if it still processes events after cutover, two systems may be doing one job. duplicate workers are especially dangerous when actions involve messages, trades or payments.

decommissioning is part of migration

the old server should not remain a mysterious hot spare forever.

after a safe observation window, stop its services, remove obsolete DNS paths, revoke credentials that no longer belong there and preserve only the backups actually needed.

a migration is complete when the new system owns the work, the old system cannot accidentally perform it and there is a tested way back if the new one fails.

building gives you a blank page. migration asks you to change the paper without disturbing the ink.