Contact salesFree trial
Blog

Source Operations sorcery for WordPress

WordPressautomation
31 January 2025
Paul Gilzow
Paul Gilzow
Developer Relations Engineer
Share

Source Operations were released on Platform.sh about 4 years ago, but were limited to those customers on Enterprise and Elite plans. On Upsun, access to Source Operations is available on all plans

If you're not familiar with Source Operations, it's a feature that allows you to specify commands that can commit changes to your project's repository when called. For example, Source Operations can be used to update your dependencies, even hooking a composer update to a cron job so that dependency updates occur automatically on a dedicated environment.

But what if you're not using composer? What if you have something like a traditional WordPress set up? 

Vanilla waiver

We refer to this case as vanilla WordPress; it’s set apart by its points of incompatibility from the way we normally do things at Upsun. WordPress developers are used to being able to SFTP into a server running WordPress to make edits or use the CLI to get updates. But WordPress's auto_update_core function just really doesn't work with the read-only filesystems you get on our app containers.

For that reason, we suggest you disable WP_AUTO_UPDATE_CORE and recommend developers use Composer. If you must go the vanilla route, you’re restricted to “update locally, commit, and push.”

But with Source Operations, it's possible to get access to a writable file system, checked out to the current branch, and make these kinds of changes. Should you do this? Well, there are plenty of caveats and edge cases we've yet to discover. Perhaps it's best to think of this as a "look what Source Operations can do" type of article, after which we can figure out together what best practices should be for these operations, as well as their limitations.

Setting up

To start off, we follow the guide for deploying a WordPress Vanilla codebase on Upsun. If you're trying to migrate your own Vanilla WordPress site, this guide will be the best resource for doing so. Once you've completed the guide, and deployed your site, login to the WordPress admin dashboard. There will be the "Updates" section in the sidebar. When an update for a plugin, a theme, or WordPress core is available, you'll see a counter notification on this tab. Normally it's at this point that we'd advise you to pull, update, commit, and push to a new environment to test those updates.

But let's try it with Source Operations instead this time.

CLI and authentication

Performing the update is going to require the use of the Upsun CLI within the environment and, therefore, an API token. Obtain a token from your "Accounts" page of the management console, and then (with the CLI installed locally) create a project-level environment variable with that token:

$ upsun variable:create -l project --prefix env: --name UPSUN_CLI_TOKEN --value "API_TOKEN" --json N --sensitive y --visible-build y --visible-runtime y

We're still going to add some restrictions that keep the operation from running on your production environment in the next section, but setting the variable project-wide allows us to make it visible at build time, which is the level we'll need for it to be visible during a Source Operation. It's at this point that we have our first big caveat: How safe is it to include an API token in your project like this?

Well, if it's just you working on the project, then there really isn't a problem here. But in practice this rarely happens, and your personal API token—although not visible through the management console—will be visible to anyone with SSH access to any of the environments on the project. Someone could use that token, if belonging to the project owner, and delete the site if they were so motivated. Which is not great.

We generally recommend using API tokens that belong to machine users¹, an Upsun account given restricted permissions to the project and whose only purpose is to run automation tasks like this.

After you've added the token as an environment variable, you can add the Upsun CLI as a build dependency to your .upsun/config.yaml file:

dependencies:
  php:
    wp-cli/wp-cli-bundle: "^2.4"
    psy/psysh: "^0.10.4"
    platformsh/cli: "*"

In other tutorials we've instructed you to download the CLI in your build hooks, but it turns out that won't really work as expected here. Source Operations take place on file systems at a state just before the build hook runs. So build dependencies are available, but if the CLI was installed later, it wouldn't be. If you'd rather not include the CLI as a build dependency in this way, you can still install it during your build hooks. But you'll also need to run the same installation command within the Source Operation definition.

The update operation

In your .upsun/config.yaml file, add the following block:

source:
  operations:
    update-wordpress:
      command: |
        set -e

        # Open a tunnel to the current environment, which allows us to get database credentials.
        ENVIRONMENT=$(git rev-parse --abbrev-ref HEAD)
        platform tunnel:open -p $PLATFORM_PROJECT -e $ENVIRONMENT -y

        # Export the relationships object and then our database credentials to the environment.
        export PLATFORM_RELATIONSHIPS="$(platform tunnel:info --encode)"
        export DB_NAME=$(echo $PLATFORM_RELATIONSHIPS | base64 --decode | jq -r ".mariadb[0].path")
        export DB_HOST=$(echo $PLATFORM_RELATIONSHIPS | base64 --decode | jq -r ".mariadb[0].host")
        export DB_PORT=$(echo $PLATFORM_RELATIONSHIPS | base64 --decode | jq -r ".mariadb[0].port")
        export DB_USER=$(echo $PLATFORM_RELATIONSHIPS | base64 --decode | jq -r ".mariadb[0].username")
        export DB_PASSWORD=$(echo $PLATFORM_RELATIONSHIPS | base64 --decode | jq -r ".mariadb[0].password")
        export DB_HOST=$DB_HOST:$DB_PORT

        # Update WordPress with the WP CLI.
        wp core --path=$PLATFORM_SOURCE_DIR/wordpress update
        wp plugin --path=$PLATFORM_SOURCE_DIR/wordpress update-all
        wp theme update --path=$PLATFORM_SOURCE_DIR/wordpress --all

        # Stage changes, committing only when updates are available.
        git add .
        STAGED_UPDATES=$(git diff --cached)
        if [ ${#STAGED_UPDATES} -gt 0 ]; then
            git commit -m "Gloriously autoupdated Wordpress."
        else
            echo "No WordPress updates found."
        fi

        # Close the connection.
        platform tunnel:close -y

Note: if you chose MySQL instead of MariaDB in the Getting started guide, or changed the name of your database service to something other than mariadb, you'll need to change the code above to reflect the name you used.

So we've got an interesting block of YAML here—let's pick it apart. When the operation is run, we're actually going to use the CLI to open a tunnel to the environment and use that tunnel to mimic the relationships array locally, same as we would in a tethered local development scenario. Having our database credentials available allows us to then run our update commands with the WordPress CLI.

Once we've done that, we run the update. Then we've got a little catch block here that checks if file changes occurred on the repository after the update. This is here because we've noticed that without it running, an update command that results in no available updates (nothing to commit, working tree clean should be great news) will sometimes cause the operation to hang there when we try to commit. This block checks if there's anything staged for commit and just exits when everything is up-to-date. Then we close our tunnel to clean up after ourselves.

After we commit and push the operation to the project, let's create a new branch² where we can dedicate to testing updates.

$ upsun environment:branch update-wordpress

Once that's successfully deployed, we can at any time run the operation on that environment³:

$ upsun source-operation:run update-wordpress

You'll see that the operation will run and, if updates are available on any plugins, themes, or WordPress core, commit them to the environment, ready for testing.

Automatic updates

It's great that we’ve created a new endpoint for the project to look for and to apply updates to our vanilla WordPress site. But I'm not going to remember to do this regularly, and since I expect you and I aren't made of different stuff, let's automate this.

Add the following to .upsun/config.yaml:

crons:
  auto-update-wordpress:
    spec: "0 1 * * *"
    cmd: |
      if [ "$PLATFORM_BRANCH" = update-wordpress ]; then
          platform environment:sync code data --no-wait --yes
          platform source-operation:run update-wordpress --no-wait --yes
      fi

Now we have a cron job that runs everyday at 1am, syncs code and data from our production site, and then runs our update operation. We've even included some branch restrictions so all of our updates happen in the same place—only on our dedicated updates environment.

Now that we’ve shown you the secrets to this Source Operations trick, we hope you’ll give it a try and then report back to us whether it was a showstopper or just a puff of smoke. 


 

¹ See https://upsun.com/pricing/#user-licenses for user licensing fees

² Unlike Platform.sh, Upsun charges only for resources used. Therefore, when you create a new environment, you'll be charged for any resources used in that environment while it is active. To keep costs to a minimum, you should consider a resource initialization strategy that best makes sense for your situation.

³ Upsun charges for build minutes and resource usage. A WordPress Vanilla source-operation build takes approximately 2 minutes, and the source operation itself can take a minute or more depending on the number of plugins and themes that need to be updated. Assuming 2 minutes to build and 3 minutes to complete, based on current prices a source operation like the example above will cost 0.006 USD or about 0.20 USD/month if you run it once a day.

⁴ Please note that you can not sync code if you are using a source integration. Code syncs between branches will need to occur at the canonical repository source. Alternatively, consider deleting the branch after every merge, and after every source operation run if there are no updates.
Discord
© 2025 Platform.sh. All rights reserved.