Aryan Jasala

WordPress VIP Go: repo structure, environments, and local dev

One repository branching into develop, preprod and production

WordPress VIP Go is Automattic’s enterprise hosting platform, and the first thing that catches people out is that a VIP repo does not look like a WordPress install. This covers the directory structure the platform expects, the four environments, getting a local one running with the VIP CLI, and the coding rules VIP actually enforces.

Eight directories, nothing else

Eight required directories

  • client-mu-plugins, plugins, themes
  • vip-config, private, images
  • docs, languages
  • All eight required, none may be removed

Any directory you add

  • Not mounted by the platform
  • Not web accessible
  • The code in it never runs

docs/ is required as well, but it is the one directory not mounted to production.

A VIP Go repo has exactly eight required directories; any directory you add is not mounted, so the code in it never runs.

What VIP Go is

Enterprise hosting at wpvip.com, built so that a site does not go down for performance reasons.

What you get in the repo is not a WordPress codebase. It is a wrapper around one. Every application lives in a private repo under the @wpcomvip GitHub organisation, based on the same starting point:

https://github.com/Automattic/vip-go-skeleton

Admins hold repo access and everything ships through git. There is no plugin installer and no theme uploader. ๐Ÿ™ƒ

The directory structure

  • client-mu-plugins/ -> always-active global plugins, because mu-plugins is not yours
  • docs/ -> documentation, and the one directory not mounted to production
  • images/ -> favicons
  • languages/ -> .po and .mo translation files, produced by making the plugin translatable in the first place
  • plugins/ -> regular plugins
  • private/ -> readable by your code, not by the public
  • themes/ -> your themes
  • vip-config/ -> vip-config.php and sunrise.php

All eight are required and must not be removed. The rule that catches people is the other direction: any directory you add that is not on that list does not get mounted, so it is not web accessible and the code in it never runs.

client-mu-plugins/ loads a single-file plugin automatically. A plugin in a subdirectory does not load at all until you require it from client-mu-plugins/plugin-loader.php.

images/ is for favicons specifically. Other public images belong in the media library or in your theme’s assets, not here.

private/ is not publicly reachable but your own code can read it:

file_get_contents( WPCOM_VIP_PRIVATE_DIR . '/sites.json' );

Treat it as read-only. The whole repo is deployed from git, so anything written at runtime is not going to survive the next deploy.

vip-config/vip-config.php is where your wp-config.php constants go. One warning that is easy to learn the hard way: it loads immediately after wp-config.php, so most WordPress functions, classes and APIs do not exist yet. Pure PHP only. ๐Ÿ™ƒ

The four environments

  • Production -> public
  • Preprod -> production-shaped, where testing happens before a release
  • Develop -> non-production, for debugging
  • Local -> your machine

Each environment tracks a specific branch of the GitHub repo, so deploying is pushing.

Every application gets exactly one production environment. Non-production environments are created in the VIP Dashboard, and how many you are allowed is set by the contract rather than by you. So the number of places you can safely break something is a commercial question. ๐Ÿคก

Installing the VIP CLI

The CLI is node based:

npm install -g @automattic/vip

Then create an environment and start it:

vip dev-env create
vip dev-env start

create walks through the options. start prints the paths and URLs to reach the environment.

Keep Node on an LTS release. Locally built assets get committed to the repo as static files, so the version you build on is the version the next person inherits. ๐Ÿ“ฆ

vip-config.php loads before WordPress
wp-config.php
vip-config.phpyour constants, pure PHP only
WordPress loadsfunctions, classes and APIs exist

vip-config.php runs immediately after wp-config.php, before most WordPress functions, classes and APIs exist.

vip-config.php holds your wp-config constants, but it runs before WordPress does, so only plain PHP works there.

The rules VIP enforces

Commits:

  • one feature or one change per commit, related changes bundled together
  • atomic -> no commit leaves the tree in a broken state
  • present tense, semantic, self descriptive

Queries:

  • use WordPress API functions instead of going at the database directly
  • $wpdb->prepare on anything that takes input
  • keep the SQL simple and do the logic in PHP, which is what scales
  • no DISTINCT, no GROUP BY, nothing that builds a temporary table
  • cache the results where caching makes sense

That second list is not a style preference. It is the platform’s performance budget, written down.

Over-fetch, then filter in PHP

The pattern VIP pushes you toward: ask for a few more rows than you need, then discard in PHP rather than making the database do the thinking.

$exclude = array( get_the_ID(), ...$suggested_posts );

$args = array(
    'posts_per_page' => 7,
    'author'         => get_the_author_meta( 'ID' ),
    'post_type'      => 'post',
    'post_status'    => 'publish',
    'post__not_in'   => get_option( 'sticky_posts' ),
    'orderby'        => 'date',
    'order'          => 'DESC',
);

$result = new WP_Query( $args );

Seven posts to end up with three: three you want, the current one, and three already suggested on the page. Then loop, skip anything in $exclude, check the post date in PHP, and break once three have printed. ๐ŸŽฃ

Environment-specific code hangs off a constant the platform sets for you, VIP_GO_APP_ENVIRONMENT. The skeleton’s own vip-config.php uses it exactly this way:

if ( ( ! defined( 'VIP_GO_APP_ENVIRONMENT' ) || ( defined( 'VIP_GO_APP_ENVIRONMENT' ) && 'production' !== VIP_GO_APP_ENVIRONMENT ) )
    && ! defined( 'WP_DEBUG' ) ) {
    define( 'WP_DEBUG', true );
}

Note the shape of that check. It treats “constant not defined” as non-production, because local environments do not define it.

PHPCS comes with the repo

The skeleton ships a .phpcs.xml.dist and a composer.json that installs three standards for you: VIP Coding Standards, WordPress Coding Standards, and PHPCompatibilityWP. ๐Ÿ“

composer install
vendor/bin/phpcs

Run it from whichever directory holds the relevant .phpcs.xml.dist, because themes and custom plugins can extend the root config with their own. If you have not set PHPCS up outside VIP before, the local setup guide covers the standalone install and phpcbf.

What this does not cover

Deploys themselves, the support ticket process for extra environments, and VIP’s own caching layer.

This started as notes taken while learning the platform in 2022 and every claim above was re-checked against the current Automattic/vip-go-skeleton repository and VIP docs before publishing. Two things had already changed in that time: the old /config directory with its .vip.[app].[environment].yml domain mapping files is gone, and extra environments come from the VIP Dashboard now rather than a support ticket. ๐Ÿ•ฐ๏ธ