Shane HobanShane Hoban

Shane Hoban

MSc. Computer Science (UCD) · Full Stack Developer

Crosshaven, Cork, Ireland
All Articles

Migrating Survais from an old VPS to Coolify

22 February 2026
ai-assistedsurvaismigrationdocker composecoolifyphpmariadbdeployment

Today I migrated Survais from an old VPS setup to a new VPS managed through Coolify, using a single Docker Compose stack for both the PHP app and MariaDB.

The main goal was operational predictability: deploy the same way every time, make first boot deterministic, and avoid fragile one-off setup steps.

What I changed

  • split runtime into two services in docker-compose.yml:
    • app (PHP/Apache)
    • db (MariaDB)
  • added service health checks and depends_on readiness behavior so app startup waits for DB health
  • switched app networking to internal expose: 80 rather than fixed host port mapping for Coolify

Database seeding strategy

The key migration decision was to bake seed SQL into a custom DB image:

  • Dockerfile.mariadb extends mariadb:10.3
  • copies ops/initdb/01-seed.sql into /docker-entrypoint-initdb.d/01-seed.sql

That means first boot on an empty volume imports data automatically, while later deploys keep existing data and skip reseeding.

I also documented recovery paths in ops/COOLIFY.md:

  • manual import if DB exists but tables are missing
  • full destructive reseed via docker compose down -v when intentionally rebuilding from scratch

PHP container hardening

I updated the app image defaults to be safer for production:

  • display_errors=Off
  • display_startup_errors=Off
  • html_errors=Off
  • log_errors=On
  • stricter error_reporting to reduce noisy deprecated/strict output for this legacy stack

That keeps user-facing output clean while still preserving server-side logging.

Why this migration matters

For legacy PHP apps, deployment failures often come from environment drift and undocumented init steps.

This migration reduced those risks by making runtime behavior explicit:

  • one compose file
  • one seeded DB path
  • clear env var contract (DB_NAME, DB_USER, DB_PASS, DB_ROOT_PASSWORD)
  • written runbook for Coolify

What is next

After landing the infrastructure migration, I followed up with widget-level compatibility and escaping fixes. I documented those in: