> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Automating Runtime and Service Tracking with Meta Version Updater

> Meta is Upsun’s shared reference layer for runtimes, services, regions, and
extensions. Learn why we built it, how it works, and how it powers the next
generation of Upsun documentation and tooling.


export const PostMeta = ({data = {}}) => {
  const {author, date} = data;
  const authors = Array.isArray(author) ? author : author ? [author] : [];
  const resolveAuthor = slug => {
    const entry = AUTHOR_MAP[slug] || ({});
    const name = entry.name || slug;
    const github = entry.github || null;
    const linkedin = entry.linkedin || null;
    const url = github ? `https://github.com/${github}` : linkedin || null;
    const avatarUrl = github ? `https://github.com/${github}.png?size=64` : null;
    return {
      name,
      url,
      avatarUrl
    };
  };
  const formattedDate = date ? new Date(date).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }) : null;
  if (authors.length === 0 && !formattedDate) return null;
  const AUTHOR_MAP = {
    "aaron-collier": {
      "name": "Aaron Collier"
    },
    "aaron-dudenhofer": {
      "name": "Aaron Dudenhofer"
    },
    "aaron-porter": {
      "name": "Aaron Porter"
    },
    "adriaan-odendaal": {
      "name": "Adriaan Odendaal"
    },
    "ajmal": {
      "name": "Ajmal Siddiqui"
    },
    "akalipetis": {
      "name": "Antonis Kalipetis"
    },
    "alexander-varwijk": {
      "name": "Alexander Varwijk"
    },
    "alicia-bevilacqua": {
      "name": "Alicia Bevilacqua"
    },
    "amelie-deguerry": {
      "name": "Amelie Deguerry"
    },
    "anacidre": {
      "name": "Ana Cidre",
      "linkedin": "https://www.linkedin.com/in/ana-cidre"
    },
    "andoni": {
      "name": "Andoni Auzmendi"
    },
    "andrei-taranu": {
      "name": "Andrei (Alex) Taranu",
      "linkedin": "https://www.linkedin.com/in/andrei-alex-taranu/"
    },
    "andrew-baxter": {
      "name": "Andrew Baxter"
    },
    "andrew-melck": {
      "name": "Andrew Melck"
    },
    "antoine-crochet-damais": {
      "name": "Antoine Crochet Damais"
    },
    "augustin-delaporte": {
      "name": "Augustin Delaporte",
      "linkedin": "https://www.linkedin.com/in/augustindelaporte/"
    },
    "branislav-bujisic": {
      "name": "Branislav Bujisic"
    },
    "carl-smith": {
      "name": "Carl Smith"
    },
    "caroline-leroy": {
      "name": "Caroline Leroy"
    },
    "cati-mayer": {
      "name": "Cati Mayer"
    },
    "catplat": {
      "name": "C Trinkwon"
    },
    "ceelolulu": {
      "name": "Celeste van der Watt"
    },
    "chadwcarlson": {
      "name": "Chad Carlson",
      "github": "chadwcarlson",
      "linkedin": "https://www.linkedin.com/in/chadwcarlson"
    },
    "chris-ward": {
      "name": "Chris Ward"
    },
    "chris-yates": {
      "name": "Chris Yates"
    },
    "christian-sieber": {
      "name": "Christian Sieber"
    },
    "christopher-lockheardt": {
      "name": "Christopher Lockheardt"
    },
    "christopher-skene": {
      "name": "Christopher Skene"
    },
    "chuck-morgan": {
      "name": "Chuck Morgan"
    },
    "corey-dockendorf": {
      "name": "Corey Dockendorf"
    },
    "crell": {
      "name": "Crell"
    },
    "damz": {
      "name": "Damz"
    },
    "dan-morrison": {
      "name": "Dan Morrison"
    },
    "davidbonachera": {
      "name": "David Bonachera",
      "github": "davidbonachera",
      "linkedin": "https://www.linkedin.com/in/davidbonachera"
    },
    "dereliahmet1": {
      "name": "Ahmet Faruk Dereli"
    },
    "devicezero": {
      "name": "Jonas Kröger",
      "github": "devicezero",
      "linkedin": "https://www.linkedin.com/in/jonaskroeger/"
    },
    "doug-goldberg": {
      "name": "Doug Goldberg"
    },
    "duncan-naves": {
      "name": "Duncan Naves",
      "github": "duncannaves",
      "linkedin": "https://www.linkedin.com/in/duncan-naves-a94423aa"
    },
    "erika-bustamante": {
      "name": "Erika Bustamante"
    },
    "fabpot": {
      "name": "Fabien Potencier"
    },
    "flovntp": {
      "name": "Florent Huck",
      "github": "flovntp",
      "linkedin": "https://www.linkedin.com/in/florenthuck"
    },
    "fred-plais": {
      "name": "Fred Plais"
    },
    "gauthier-garnier": {
      "name": "Gauthier Garnier"
    },
    "gilzow": {
      "name": "Paul Gilzow"
    },
    "gmoigneu": {
      "name": "Guillaume Moigneu",
      "github": "gmoigneu",
      "linkedin": "https://www.linkedin.com/in/guillaumemoigneu/"
    },
    "gregqualls": {
      "name": "Greg Qualls"
    },
    "guguss": {
      "name": "Augustin Delaporte"
    },
    "haylee-millar": {
      "name": "Haylee Millar"
    },
    "ivana-kotur": {
      "name": "Ivana Kotur"
    },
    "jackrabbithanna": {
      "name": "Mark Hanna"
    },
    "jared-wright": {
      "name": "Jared Wright",
      "github": "jww-sh",
      "linkedin": "https://www.linkedin.com/in/jaredwaynewright"
    },
    "jessica-orozco": {
      "name": "Jessica Orozco"
    },
    "joey-stanford": {
      "name": "Joey Stanford"
    },
    "john-grubb": {
      "name": "John Grubb"
    },
    "jonas-kruger": {
      "name": "Jonas Kruger"
    },
    "kathryn-frazer": {
      "name": "Kathryn Frazer"
    },
    "kemiojo": {
      "name": "Kemi Elizabeth Ojogbede"
    },
    "kieronsambrook-smith": {
      "name": "Kieronsambrook Smith"
    },
    "laurent-arnoud": {
      "name": "Laurent Arnoud",
      "linkedin": "https://www.linkedin.com/in/laurent-arnoud-861b44121/"
    },
    "letoya-boyne": {
      "name": "Letoya Boyne"
    },
    "lolautruche": {
      "name": "Jérôme Vieilledent"
    },
    "lyly-lepinay": {
      "name": "Lyly Lepinay"
    },
    "manauwar-alam": {
      "name": "Manauwar Alam"
    },
    "marc-antoine-porri": {
      "name": "Marc Antoine Porri"
    },
    "maria-antinkaapo": {
      "name": "Maria Antinkaapo"
    },
    "maria-de-anton": {
      "name": "Maria De Anton"
    },
    "mark-dorison": {
      "name": "Mark Dorison"
    },
    "markus-hausammann": {
      "name": "Markus Hausammann"
    },
    "mary-thomas": {
      "name": "Mary Thomas"
    },
    "mathias-bolt-lesniak": {
      "name": "Mathias Bolt Lesniak"
    },
    "mathieu-strauch": {
      "name": "Mathieu Strauch"
    },
    "matthias-van-woensel": {
      "name": "Matthias Van Woensel",
      "linkedin": "https://www.linkedin.com/in/matthias-van-woensel-267a069"
    },
    "maz-mohammadi": {
      "name": "Maz Mohammadi"
    },
    "michael-sharp": {
      "name": "Michael Sharp"
    },
    "mupsi": {
      "name": "Marine Gandy"
    },
    "natalie-harper": {
      "name": "Natalie Harper"
    },
    "ngommenginger": {
      "name": "Nicolas Gommenginger",
      "linkedin": "https://www.linkedin.com/in/nicolas-gommenginger"
    },
    "nicholas-bennison": {
      "name": "Nicholas Bennison"
    },
    "nicholas-vahalik": {
      "name": "Nicholas Vahalik"
    },
    "nick-hardiman": {
      "name": "Nick Hardiman"
    },
    "nickanderegg": {
      "name": "Nickanderegg"
    },
    "nicolas-grekas": {
      "name": "Nicolas Grekas",
      "github": "nicolas-grekas",
      "linkedin": "https://www.linkedin.com/in/nicolasgrekas/"
    },
    "niti-malwade": {
      "name": "Niti Malwade"
    },
    "opensocialteam": {
      "name": "Opensocialteam"
    },
    "ori-pekelman": {
      "name": "Ori Pekelman"
    },
    "otavio-santana": {
      "name": "Otavio Santana"
    },
    "palwandi": {
      "name": "Pawan Alwandi",
      "github": "pawpy",
      "linkedin": "https://www.linkedin.com/in/pawanalwandi"
    },
    "patrick-boest": {
      "name": "Patrick Boest"
    },
    "patrick-dawkins": {
      "name": "Patrick Dawkins",
      "github": "pjcdawkins",
      "linkedin": "https://www.linkedin.com/in/patrickdawkins"
    },
    "patrick-klima": {
      "name": "Patrick Klima"
    },
    "pjcdawkins": {
      "name": "Pjcdawkins"
    },
    "prineet-kaurbhurji": {
      "name": "Prineet Kaurbhurji"
    },
    "quentin-sinig": {
      "name": "Quentin Sinig"
    },
    "ralt": {
      "name": "Florian Margaine",
      "github": "ralt",
      "linkedin": "https://www.linkedin.com/in/florian-margaine-43971136"
    },
    "ramanathanramakrishnamurthy": {
      "name": "Ramanathanramakrishnamurthy"
    },
    "remi-lejeune": {
      "name": "Rémi Lejeune"
    },
    "ribel": {
      "name": "Taras Kruts"
    },
    "robert-douglass": {
      "name": "Robert Douglass"
    },
    "rudy-weber": {
      "name": "Rudy Weber"
    },
    "ryan-hicks": {
      "name": "Ryan Hicks"
    },
    "sabri-helal": {
      "name": "Sabri Helal"
    },
    "savannah-bergeron": {
      "name": "Savannah Bergeron"
    },
    "shannon-vettes": {
      "name": "Shannon Vettes"
    },
    "shawn-ogasawara": {
      "name": "Shawn Ogasawara",
      "linkedin": "https://www.linkedin.com/in/shawn-ogasawara-83a9a0/"
    },
    "shawna-spoor": {
      "name": "Shawna Spoor"
    },
    "shedrack-akintayo": {
      "name": "Shedrack Akintayo"
    },
    "simon-ruggier": {
      "name": "Simon Ruggier"
    },
    "sophie-van-der-kindere": {
      "name": "Sophie Van Der Kindere"
    },
    "stefanos-thampis": {
      "name": "Stefanos Thampis"
    },
    "stephen-weinberg": {
      "name": "Stephen Weinberg"
    },
    "sukhman-virk": {
      "name": "Sukhman Virk"
    },
    "sumaira-nazir": {
      "name": "Sumaira Nazir"
    },
    "sumer": {
      "name": "Sümer Cip"
    },
    "syed-raza": {
      "name": "Syed Raza"
    },
    "tamara-bacchia": {
      "name": "Tamara Bacchia"
    },
    "tara-arnold": {
      "name": "Tara Arnold"
    },
    "theosakamg": {
      "name": "Mickael Gaillard",
      "github": "theosakamg"
    },
    "thomasdiluccio": {
      "name": "Thomas di Luccio"
    },
    "tim-anderson": {
      "name": "Tim Anderson"
    },
    "tom-helmer-hansen": {
      "name": "Tom Helmer Hansen"
    },
    "tylermills": {
      "name": "Tyler Mills"
    },
    "upsun": {
      "name": "Upsun"
    },
    "veronika-tolkachova": {
      "name": "Veronika Tolkachova",
      "linkedin": "https://www.linkedin.com/in/veronika-tolkachova-169167a2"
    },
    "vince-parker": {
      "name": "Vince Parker"
    },
    "vinnie-russo": {
      "name": "Vincenzo Russo"
    },
    "vrobert78": {
      "name": "Vincent Robert",
      "github": "vrobert78",
      "linkedin": "https://www.linkedin.com/in/vincent-robert-498a883"
    },
    "yuriy-babenko": {
      "name": "Yuriy Babenko"
    },
    "yuriy-gerasimov": {
      "name": "Yuriy Gerasimov"
    }
  };
  return <div className="post-meta">
      {(authors.length > 0 || formattedDate) && <div className="post-meta-info">
          {authors.length > 0 && <div className="post-meta-authors">
              {authors.map(slug => {
    const {name, url, avatarUrl} = resolveAuthor(slug);
    const inner = <>
                    {avatarUrl && <img src={avatarUrl} alt={name} className="post-meta-avatar" />}
                    <span className="post-meta-author-name">{name}</span>
                  </>;
    return url ? <a key={slug} href={url} target="_blank" rel="noopener noreferrer" className="post-meta-author">
                    {inner}
                  </a> : <span key={slug} className="post-meta-author">{inner}</span>;
  })}
            </div>}
          {authors.length > 0 && formattedDate && <span className="post-meta-separator" aria-hidden="true">·</span>}
          {formattedDate && <span className="post-meta-date">{formattedDate}</span>}
        </div>}
    </div>;
};

<PostMeta data={{ author: ["flovntp", "theosakamg"], date: "2026-04-01T15:22:00.000Z", image: "/images/posts/how-tos/meta-version-updater-introduction/red-background.webp" }} />

From our previous article about [Meta reference source introduction](https://devcenter.upsun.com/posts/meta-reference-source-introduction/), once you centralise reference data, the next challenge is keeping it accurate.

Upsun supports a large and evolving ecosystem of runtimes, services, and regions. Tracking upstream releases, supported versions, and deprecated images manually quickly becomes unsustainable.

This is where our internal tool named **Meta Version Updater** comes in.

## The challenge: staying in sync with reality

Today, Upsun manages:

* **23 services**
* **9 runtimes**
* **15 public regions**
* **PHP extensions** from PHP 5.4 up to PHP 8.5

Each of these evolves independently, driven by:

* Official upstream releases
* Security and maintenance policies
* Internal build pipelines
* Git branches representing supported versions

Before Meta Version Updater:

* Engineers manually cross-checked multiple sources
* Updates required hand-edited files
* Documentation changes meant additional pull requests
* Drift between reality and documentation was hard to detect

## Introducing Meta Version Updater

**Meta Version Updater** is an internal automation tool designed to continuously reconcile *what exists* with *what we support*.

<img src="https://mintcdn.com/upsun-c9761871/YEOzW9DxF0TmbjEQ/images/posts/how-tos/meta/meta-version-updater-interface.png?fit=max&auto=format&n=YEOzW9DxF0TmbjEQ&q=85&s=7629d667e167f94a122aa431ef8622b0" alt="Screenshot of the Meta Version Updater interface" width="2580" height="1652" data-path="images/posts/how-tos/meta/meta-version-updater-interface.png" />

At a high level it:

* Collects **official upstream versions**
* Compares them with **Upsun-supported images** (Git branches)
* Detects missing, outdated, or deprecated versions
* Updates Meta and documentation through automated pull requests

Beyond automating version management, this tool enables us to keep track of official releases. By monitoring new versions as they become available, we can make informed decisions about where to invest our efforts to provide timely access to the most valuable updates for our users.

## Technical stack

Meta Version Updater relies on a modern and efficient technical stack:

* **Node.js v24**: The main engine of the application, providing speed and compatibility with the latest JavaScript features.
* **PostgreSQL 18**: A robust relational database to store metadata, versioning, and update history.

**Allocated resources:**

* **Application**: 0.5 CPU, 0.218 GB RAM, 500MB disk space
* **Database**: 0.5 CPU, 1.375 GB RAM, 500MB disk space

This configuration ensures smooth operation, fast data ingestion, and the ability to scale as Upsun's ecosystem grows.

<img src="https://mintcdn.com/upsun-c9761871/YEOzW9DxF0TmbjEQ/images/posts/how-tos/meta/meta-version-updater-resources.png?fit=max&auto=format&n=YEOzW9DxF0TmbjEQ&q=85&s=6ea765bbc6c7eee30862109d3428c2c1" alt="Resources used by the Meta Version Updater tool" width="2416" height="478" data-path="images/posts/how-tos/meta/meta-version-updater-resources.png" />

By leveraging Upsun, we were able to deliver this tool much faster and with greater agility.
The platform's flexibility and automation capabilities allowed us to focus on core features and respond quickly to evolving requirements.

## Nightly ingestion pipeline (high level)

Every night, Meta Version Updater ingests data from multiple sources:

1. **Official upstream versions**
   * Runtime and service release sources
   * Used as the reference for what *could* be supported

2. **Upsun internal repositories**
   * Git branches represent supported versions
   * Missing branches highlight unsupported or pending versions

3. **Upsun APIs**
   * Regions are fetched directly from the Upsun API
   * IP addresses are added manually where required

```mermaid theme={null}
flowchart TB
  S[Scheduler: nightly run] --> A[Fetch upstream versions: runtime and service releases]
  S --> B[Fetch internal branches: branch to version]
  S --> C[Fetch Upsun regions: Upsun API]
  A --> D[Compare and detect gaps: missing versions, candidates]
  B --> D
  C --> E[Enrich regions: manual addition of IPs]
  D --> F[Generate updates: registry.json and derived data]
  D --> G[Update PHP extension data: php_extensions.yaml]
  E --> H[Update region data: regions.yaml]
  F --> I[Open PRs: upsun/meta and platformsh/docs]
  G --> I
  H --> I
```

## What gets updated automatically

The tool produces structured updates for:

* **`registry.json`**
  * Supported and deprecated service/runtime versions
  * Used by:
    * [https://docs.upsun.com/add-services.html](https://docs.upsun.com/add-services.html)
    * [https://docs.upsun.com/languages.html](https://docs.upsun.com/languages.html)

* **`php_extensions.yaml`**
  * Default and available PHP extensions per version
  * Displayed here:
    * [https://docs.upsun.com/languages/php/extensions.html](https://docs.upsun.com/languages/php/extensions.html)

* **Region data**
  * Public regions and metadata
  * Displayed here:
    * [https://docs.upsun.com/development/regions.html#regions](https://docs.upsun.com/development/regions.html#regions)

***

## Human review stays in the loop

Automation does not remove humans from the process.

For every detected change, Meta Version Updater:

* Opens **pull requests automatically**
* Targets:
  * `upsun/meta`
  * `platformsh/docs`
* Highlights diffs and version changes clearly

This guarantees:

* Reviewability
* Traceability
* Intentional changes only

***

## Detecting drift before it hurts

One of the most valuable outcomes is early drift detection:

* Official versions exist but no internal branch → missing support
* Supported versions without upstream maintenance → deprecation candidates
* Documentation inconsistencies → caught before release

This gives teams a clear, actionable view of platform support.

## What’s coming next: Slack summaries via n8n

We’re extending the workflow further.

An **n8n-based automation** is currently in development to:

* Send Slack notifications when a PR with image updates is opened
* Post a human-friendly summary of changes
* Improve visibility across internal teams

The goal is straightforward:
make changes visible, understandable, and actionable—without manual coordination.

## Links

* Meta API docs (Scalar): [https://meta.upsun.com/api-docs](https://meta.upsun.com/api-docs)
