With the impending and extremely frustrating slow murder of Heroku I found myself looking for alternative places to run our apps built on the extremely Heroku-friendly PHP8 + PosrgreSQL.

The key requirements for me are:

  • Fully managed Postgres
  • No fucking about building images, sorry – I want a pre-configured and secure PHP8 environment that’s ready for production
  • All managed via the CLI and deployed from git

One that looks promising to me is Upsun, formerly platform.sh. Here’s what I learned from a few hours of cloning an existing app across from Heroku – I make no claims that this is best, or even good, practice. It’s just what I found was needed to get an app deployed for testing.

Key files

All the key configuration of your app is handled via several YAML files. Before anything else, get a decent YAML linter for your IDE or at least stop it from converting spaces to tabs, which will wreck everything.

The config file names seem a bit stuck between the Upsun and Platform branding. But I found that you need, at a minimum:

.platform.app.yaml – in the root
/.platform/routes.yaml
/.platform/services.yaml

Services

In Heroku you would add services via the addons system – so, deploy an app onto X dynos, also provision a Postgres server, link them up. In Upsun you add Postgres as a service in your project. In services.yaml that could be, at a minimum:

postgresql:
  type: postgresql:18
  disk: 256

Note that this just deploys the service – it does not add a relationship between it and your environment, so the database is just sort of floating there being useless for now. Those links are configured in:

app.yaml

The .platform.app.yaml file sets all kinds of top-level config for your app. Here’s a minimal sample:

name: someapp 
type: 'php:8.5'
disk: 2048
web:
  locations:
    '/':
      root: 'web'
      passthru: true
      index: ["index.php"]
      allow: true
relationships:
  postgresql:
runtime:
  extensions:
    - pgsql

Note the top level app name being set, me forwarding all traffic to the /web/ directory as per my old heroku setup, and then two items required to make Postgres work: adding the pgsql extension to PHP and a relationship linking the database to the app. Obviously update the extensions line if you use PDO.

routes.yaml

Add your routes to, appropriately, the routes.yaml file. Here’s an absolutely minimum one, sending any https traffic:

"https://{default}/":
  type: upstream
  upstream: "someapp:http"

Config variables

There are lots of ways to set config vars, which can be per-project or per-environment. You set them from the CLI:

upsun variable:create --level environment --name VARIABLE_NAME --value VARIABLE_VALUE

The level can be environment or project, which is neat.

DATABASE_URL

This bit proved trickier than I expected. Upsun automatically exposes key variables such as your database credentials, but I found that they needed a bit of rewriting to get them working with my existing code, which expected a Heroku-like DATABASE_URL.

Create this in root as a file called .environment:

export RELATIONSHIPS_JSON="$(echo "$PLATFORM_RELATIONSHIPS" | base64 --decode)"

export DB_CONNECTION="$(echo "$RELATIONSHIPS_JSON" | jq -r '.postgresql[0].scheme')"
export DB_USERNAME="$(echo "$RELATIONSHIPS_JSON" | jq -r '.postgresql[0].username')"
export DB_PASSWORD="$(echo "$RELATIONSHIPS_JSON" | jq -r '.postgresql[0].password')"
export DB_HOST="$(echo "$RELATIONSHIPS_JSON" | jq -r '.postgresql[0].host')"
export DB_PORT="$(echo "$RELATIONSHIPS_JSON" | jq -r '.postgresql[0].port')"
export DB_DATABASE="$(echo "$RELATIONSHIPS_JSON" | jq -r '.postgresql[0].path')"

export DATABASE_URL="postgresql://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_DATABASE}"

.. and you should get a Heroku-code friendly DATABASE_URL. I’m aware that this could be made simpler, but for now it works (hitting an error here? note the postgresql:// bit of the URL, manually added by me).

URL Rewrites

I’m used to Apache’s URL rewriting via .htaccess. Upsun uses nginx, so you need to add rewrites to .platform.app.yaml, where they go under rules within web. An example:

web:
  locations:
    '/':
      root: 'web'
      passthru: true
      index: ["index.php"]
      allow: true
      rules:
        '^/something/(?<somevar>[^/]+)/$':
          passthru: '/somefile.php?somevar=$somevar

That’ll rewrite /something/abc123/ to /somefile.php?somevar=abc123

Based on the above, my test app was live and functional – connected up neatly to Postgres, with API keys properly managed, deployable via git. Adding a custom domain and SSL is simple, and you can open a tunnel to access the database for import.

I’m not yet sure if we’ll be using Upsun going forward, but it certainly looks like a well thought-out option and one that we’ll be testing. If you’re facing the same where-to-go-now dilemma for the coming post-Heroku world, I hope that some of the above might be helpful.