Back to projects
Oct 20, 2026 • Project

GCP → OVH Migration: Symfony Extranet

Migration of a Symfony extranet and its showcase site from GCP to a dedicated OVH Public Cloud project. Infrastructure in Terraform, apps on Docker Compose behind Traefik, GitHub Actions deployment, production cutover in 40 minutes.

TerraformOVH Public CloudOpenStackDockerTraefikFrankenPHPMySQLRedisGitHub Actions
Context
A Symfony extranet and its showcase site hosted on GCP, to be moved to OVH in a French region without breaking production.
My role
Infrastructure written in Terraform, Docker Compose stack, GitHub Actions deployment, then the production cutover.
Outcome
Production runs on OVH. Full cutover in 40 minutes: database export, import, DNS switch.

The project

The extranet (Symfony) and its showcase site were running on GCP. They had to move to OVH, in a French region, without breaking production.

All the Terraform is written by hand, without an off-the-shelf module, to know exactly what each resource creates.

Architecture

A dedicated OVH project

At first, the infrastructure was going into PharmaGDD’s existing Public Cloud project. It ended up in its own OVH project: separate OpenStack and API credentials, separate billing, and a mistake on this project can’t touch the main infrastructure.

This took a bit more work at the start: new credentials, registry and S3 access to set up again.

Private network + bastion

The application VM sits in a dedicated private network (vRack/VLAN), separate from PharmaGDD’s. SSH access only goes through a bastion. In the security group, only 80 and 443 are open to the public, and 22 is only open from the bastion.

Internet ──► Traefik (80/443) ──► FrankenPHP (Symfony extranet)
                               └─► SPA container (showcase site)
Admin ──► Bastion ──SSH──► App VM (private network)

Terraform

The Terraform lives in its own repo. The ovh provider handles the project, network and DNS; OpenStack handles instances and security groups. You have to follow the network → subnet → instance order, and know what depends on the OVH API and what depends on OpenStack.

The VM is then set up (Docker, Compose, user, /opt/... layout) by a script run over SSH through the bastion.

Application stack

Everything runs on Docker Compose on the VM:

CI/CD

Deployment goes through GitHub Actions, with a self-hosted runner shared at the organization level. There’s no runner on the production VM.

The data cutover

Replication, studied then dropped

GCP production writes to MySQL until the very last day. My first proposal was to use replication: initial copy with a GTID position, the OVH database as a replica of GCP production, then promote the replica on cutover day. The outage would have been limited to the catch-up time.

I studied whether it was feasible. The two MySQL servers sat on two different networks: for the OVH replica to read GCP production, the MySQL port had to be opened to the public, with TLS and IP filtering, all on the production database. Too many constraints and too much exposure to save a few dozen minutes of downtime.

What we did

A simpler, one-shot cutover:

  1. Back up and export the GCP production database.
  2. Import the export into the OVH MySQL.
  3. Switch DNS to OVH.

The whole thing took 40 minutes.

What I take away

The most elegant solution was not the right one here. For an extranet, 40 minutes of planned downtime beat a production database exposed to the internet for the length of the migration. The study still paid off: it let us decide on concrete arguments rather than gut feeling.