Moving a Django Website from Azure to Google Cloud: The Details That Matter
A cloud migration can look finished before it is safe to call it finished. The homepage loads on the new server, the certificate looks valid, and the old virtual machine is still available. But where does the next contact-form submission go? Will the certificate renew? Does the new server contain the latest upload?
During Comquest’s recent move of its Django website from Azure to Google Cloud, those were the questions that mattered. The application used Apache, mod_wsgi, a Python virtual environment, SQLite, and locally stored media. This was a move between virtual machines, with the application architecture kept largely intact.
The useful lesson was how to prove the new environment was ready—and how to keep data consistent while visitors could still reach the old address.
1. Inventory the running application before copying files
Start with the machine that actually serves production. A deployment document may describe the original setup; the running server tells you what the application depends on today.
We checked the active services, listening ports, Apache virtual hosts, Python packages, database location, media directories, certificate configuration, and scheduled jobs. That established the migration’s scope and confirmed that the application did not depend on a separate database server.
- Application: code, configuration, secrets, and installed dependencies.
- Data: database, uploaded files, and any other persistent application state.
- Delivery: web-server configuration, WSGI integration, static files, redirects, and certificates.
- Operations: scheduled tasks, renewal timers, permissions, logs, and recovery copies.
This inventory also defines what should stay specific to each cloud. Network configuration, guest agents, SSH access, and operating-system files should be configured for the destination rather than overwritten with an image of the source server.
2. Recreate the Python environment on the destination
Both servers ran Ubuntu, but they used different Ubuntu releases. The source application ran with Python 3.12; the destination’s default Python was older. Copying the application directory alone would not reproduce the working environment.
We installed a compatible Python runtime, recreated the virtual environment, and installed the application’s recorded dependency versions. We also built mod_wsgi for that interpreter. The Python used by Apache needs to be compatible with the application’s virtual environment.
Python’s documentation treats virtual environments as environments to recreate rather than move. Keeping a copy can help recovery, but rebuilding on the destination makes the runtime and installation paths explicit.
Before serving requests, check the application and its migration state:
python manage.py check
python manage.py migrate --check
Also review Django’s production deployment checklist and run python manage.py check --deploy against the intended production settings. A successful import does not establish that host restrictions, secrets, HTTPS settings, and static-file handling are correct.
3. Keep the database and uploads consistent
For this site, SQLite and uploaded media were both stored on the server. They needed to move as a consistent set. A copied database that refers to an upload missing from the destination is an incomplete migration.
We first copied the workload to prepare and test the destination while Azure continued serving visitors. That first copy was a staging snapshot. It could not be assumed to include submissions or uploads received afterward.
For the final sync, we briefly stopped the source application, transferred its current application data, and started the destination. This involved a short interruption; it was not a zero-downtime migration. Afterward, Google Cloud became the application’s writable environment.
The practical rule is to decide explicitly when the old application stops accepting writes and when the new application starts. For a small site, a controlled pause can be appropriate. A larger or busier system may require replication or another migration method designed for its availability requirements.
4. Test the destination before changing public DNS
You do not need to change public DNS to test a new web server under the real hostname. For example, curl’s --resolve option can direct a request to a chosen IP while retaining the domain in the URL.
# Documentation-only hostname and IP; replace both for your environment.
curl --fail --resolve example.com:443:203.0.113.10 https://example.com/
Keep certificate verification enabled. An HTTPS test should check the domain’s certificate as well as the response.
Our checks covered the homepage, service pages, contact form, blog, tools, sitemap, robots file, admin login, and representative static assets. We compared database record counts, checked SQLite integrity, and matched checksums for the copied media and static files.
Use more than one kind of evidence. A homepage can render while database records are missing. Matching record counts can still leave you with broken file permissions. Test a normal application write as well, using clearly identified verification data that you remove afterward.
5. Use one writable application during DNS propagation
Changing a DNS record does not move every visitor at the same instant. Some clients can continue reaching the old address while others reach the new one. If each server writes to its own local database, the records can diverge.
After the final sync, we changed the old Azure web server into a temporary HTTPS reverse proxy to Google Cloud. Visitors reaching either address would therefore use the destination application and database.
This forwarding path needed its own test. Our first check found that the proxy’s certificate-verification depth was too small for the destination’s certificate chain. We corrected that setting while retaining certificate and hostname verification, then repeated the public-page checks through Azure.
If application controls depend on client IP addresses, check that behavior too. Trust forwarded address headers only from a known proxy; a header supplied directly by an arbitrary visitor should not override the connection’s client address.
6. Verify certificate renewal, not just today’s HTTPS
An existing certificate can make a migrated site look healthy even when its renewal process is broken. The certificate files, renewal configuration, validation method, web-server integration, and scheduled task all need to work on the destination.
We checked the renewal timer and completed a simulated renewal for both the main domain and its www alias. Certbot documents the renewal dry run:
sudo certbot renew --dry-run
A successful dry run checks renewal under the current routing arrangement. If you change that arrangement afterward, make sure the validation path remains reachable.
Before deleting the old virtual machine
The old server should remain available until public DNS points to the destination, the previous record’s TTL has elapsed, and the application has been checked through its normal public address. Confirm that the destination uses a reserved static external IP before relying on that address in DNS.
- Verify the latest records and uploads are present on the destination.
- Check normal submissions, redirects, static files, and uploaded media.
- Confirm certificate renewal and required scheduled jobs are enabled.
- Keep a protected recovery copy and know how to restore it.
- Document which environment now owns application writes.
Rollback also needs a data plan. Once the destination has accepted new submissions, restarting the old application with its pre-cutover database would take you back to stale data. A return to the old environment must account for the writes and uploads received since the move.
For Comquest, the strongest evidence came from matching the data, testing real request paths, and checking operations beyond the first page load. Those checks make a migration’s outcome much easier to assess.
Planning a cloud migration? Contact Comquest to assess your application, plan the move, and verify the result.