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 and a MySQL cutover through replication.

TerraformOVH Public CloudOpenStackDockerTraefikFrankenPHPMySQLRedisGitHub Actions

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

GCP production writes to MySQL until the very last day, so a plain dump/restore would have meant a long outage. The cutover relies on replication instead:

  1. Initial copy of the database (dump with GTID position).
  2. The OVH MySQL becomes a replica of GCP production. The connection goes from OVH to GCP, filtered on OVH’s egress IP and encrypted with TLS. The OVH database stays in super_read_only so nothing can write to it by mistake.
  3. On cutover day: production goes read-only, wait for lag to hit zero, promote the replica, then switch DNS (TTL lowered a few days before).

Production is only down while the replica catches up, which takes a few minutes.