• Contact us
  • Docs
  • Login
Watch a demoFree trial
Blog
Blog
BlogProductCase studiesNewsInsights
Blog

Why your Drupal or Django project keeps breaking in staging (and how to fix it structurally)

DrupalDjangoopen sourceIaCconfiguration
26 June 2026
Share

Beyond hosting: moving from framework management to application delivery

TL;DR

  • Most open-source setups quietly turn developers into part-time infrastructure managers: maintaining staging environments, syncing runtime versions, wiring up services.
  • Standard infrastructure-as-code solves configuration drift, but leaves ongoing maintenance (OS patching, certificate rotation, service updates) with the team.
  • A single config file, like .upsun/config.yaml on Upsun, describes what your application needs; the platform handles everything below that line, permanently.
  • When the environment is version-controlled and platform-maintained, environment debugging stops being a category of work.

There's a particular kind of afternoon that most backend developers know well. You're mid-feature, something actually interesting, the kind of work you took the job for, and then a Slack message arrives. Staging is broken. The PHP version doesn't match production. Someone's composer install pulled a dependency that conflicts with the shared environment. Can you look at it?

Four hours later, you haven't touched a single thing you planned to work on for that day and all your time has been spent fixing your CI/CD pipeline.

It's a predictable result of treating a platform like a hosting account, not bad luck.

What "just hosting" actually costs you

Key takeaway: The cost of environment drift shows up less in hours per incident than in broken focus: the deep work that's expensive to restart every time a Slack message pulls someone out of it.

Most developers don't think of themselves as infrastructure managers, but a typical open-source project setup asks them to be one anyway. You're maintaining a shared staging environment, manually specifying runtime versions in deployment configs, wiring up database services, and periodically dealing with the fallout when something drifts between environments.

None of that is the job. It's the scaffolding around the job.

The scaffolding problem gets worse as stacks get more complex. A Drupal site with Redis for caching and MariaDB on the backend isn't complicated to run once it's configured, but getting to a clean, reproducible configuration takes real effort, and keeping it in sync across local, staging, and production takes ongoing vigilance. Throw in a second developer and things compound. A third, and you've probably got environment conflicts as a recurring agenda item.

The hidden cost is the interruption pattern, not the time any single incident takes. Context-switching out of focused work is disproportionately expensive because of what it displaces: the kind of deep work that takes roughly 20 minutes to drop back into. Multiply that across a team over a quarter and the cost stops looking like a stack of support tickets and starts looking like features that ship slower, because the people building them keep getting pulled away.

The infrastructure-as-code answer most teams stop short of

Key takeaway: Most IaC implementations stop at provisioning. You get a reproducible environment, but OS patching, certificate rotation, and service updates still land on the team. The full version of this idea is a platform that takes over everything below the application layer, not just at setup, but on an ongoing basis.

The standard advice here is "infrastructure as code": define your environment declaratively, check it into version control, and stop letting configuration drift happen. That's correct, as far as it goes. The problem is that most implementations stop at provisioning. You get a reproducible environment, but you're still responsible for the underlying services: patching the OS, rotating certificates, managing database versions.

You've reduced drift. You haven't reduced maintenance.

The more complete version of that idea is a config file that describes what your application needs, and a platform that takes responsibility for everything below that line, not just at provisioning time, but on an ongoing basis.

Upsun's approach to this is a single config file at .upsun/config.yaml. For a Drupal project with Redis and MariaDB, it looks roughly like this:

applications:
  drupal:
    type: php:8.3                  # exact runtime version, consistent across every environment
    relationships:
      database: "db:mysql"         # wires the app to the database service below
      cache: "redis:redis"         # wires the app to the Redis service below
    mounts:
      "/web/sites/default/files":
        source: local
        source_path: files

services:
  db:
    type: mariadb:10.11            # platform manages patching within this version
  redis:
    type: redis:7.2                # same: version pinned, maintenance handled

 

That's the whole stack. The runtime version, the services, the relationships between them. The platform handles OS patching, SSL rotation, service updates within the defined versions, and the networking between containers. You don't touch any of that, not at setup, and not later.

The PHP version in the config is the PHP version in production. Not approximately. Not "we think so." Exactly.

The same file shape covers a Django project on Python. Different runtime, different database, identical structure:

applications:
  django:
    type: python:3.12              # exact runtime version, consistent across every environment
    relationships:
      database: "db:postgresql"    # wires the app to the PostgreSQL service below
      cache: "redis:redis"         # wires the app to the Redis service below
    mounts:
      "/app/media":
        source: local
        source_path: media

services:
  db:
    type: postgresql:16            # platform manages patching within this version
  redis:
    type: redis:7.2                # same: version pinned, maintenance handled

 

Same single file, same division of labor. The runtime and services change with the stack; what the platform takes off your plate doesn't.

Why this matters more for open-source frameworks

Key takeaway: Open-source frameworks are flexible by design, which means deployment decisions fall to the team by default. A version-controlled config file makes those decisions explicit and traceable, so "what's running in production?" has an answer that doesn't require SSHing into anything.

Proprietary SaaS products tend to be opinionated about their own deployment. Open-source frameworks are, by design, flexible, which means the deployment decisions land on you.

That flexibility is mostly a feature. It's why Drupal runs on shared hosting and in hardened government infrastructure. It's why Django powers startups and large financial institutions simultaneously. But it also means there's no default answer to "how should this be deployed," which means every team makes those decisions from scratch and then lives with the consequences.

The common failure mode here is accumulated pragmatism, not incompetence. You make a reasonable decision early: a shared staging server, a manual deployment script, a cron job that works, and then you inherit it forever. The decision made sense when there was one developer and one site. By the time there are five developers and six sites, it's technical debt with no obvious moment to pay it down.

A version-controlled config file doesn't eliminate those decisions, but it surfaces them. What runtime do you actually need? Which services? When those are explicit and checked into version control, the question "what's running in production?" has an answer that doesn't require SSHing into anything.

Making the infrastructure layer boring

Key takeaway: The aim here is infrastructure that stops asking for attention, not more powerful tooling. When the platform handles the routine work, what's left is the application.

The goal here is infrastructure that stops requiring attention.

A lot of developer tooling tries to make infrastructure more powerful or more configurable. That's sometimes what you need. More often, you need something that handles the routine work without asking you to think about it, and that gets out of the way when you're debugging something in the application layer, not the platform layer.

When the environment is defined in code, reproducible across branches, and maintained by the platform rather than the team, the category of problems that look like "staging is broken" mostly disappears. What's left is the application. Which is, after all, what you're actually there to build.

Already have an existing setup?

The config file describes a target state, not a migration path. If you're running Drupal on a managed server with a deployment script you've maintained for three years, moving to a platform config is a matter of writing down what you're already running: the PHP version, the services, the relationships between them. The config becomes the source of truth for a setup that currently lives in your head and a README that's six months out of date.


 

Frequently asked questions (FAQs)

Do I still control my PHP or Python version?

Yes. You specify the exact version in .upsun/config.yaml. The platform ensures that version is consistent across every branch and environment, and handles security patching within it. You're not handing over control; you're making the decision once and having it enforced everywhere.

What happens to the underlying OS and certificates?

The platform manages OS hardening, kernel patching, and SSL certificate rotation automatically. These aren't things you configure; they're handled below the line your config file defines.

Can this work with decoupled or headless stacks?

Yes. You can define multiple applications in a single config file (a Next.js frontend and a Drupal backend, for example) and the platform handles the networking and isolation between them. No separate cloud accounts or manual networking config required.

Why doesn't my existing deployment script cover this?

Scripts handle what you've already anticipated. The maintenance gap they leave is everything else: runtime version drift, certificate expiry, service compatibility across environments. A platform-managed config file covers the ongoing work, not just the initial setup.

What does "the platform maintains it" actually mean day to day?

In practice: you update the config file when you want to change something. The platform applies that change consistently. Between changes, it handles patching and updates without requiring input from the team. The operational surface area shrinks to the application itself.

Stay updated

Subscribe to our monthly newsletter for the latest updates and news.

Your greatest work
is just on the horizon

Free trial