GitHub Actions is a continuous integration/delivery platform that allows you to automate all kinds of things, courtesy of GitHub's servers. Ghost already provides an official GitHub Action for theme deployment.
With this action configured, anytime you push an update to your theme, that update will be sent directly to your website. No upload required.
Setting up the GitHub Action couldn't be simpler. With the Ghost VS Code extension—you have the extension installed, right?—choose Ghost: Add GitHub Action from the command palette. The extension will set up everything for you. Add your secrets to GitHub, and the next time you push to GitHub, your theme changes will be pushed to your Ghost site automatically.
You can also manually set up the Deploy Action by adding a .github
folder to your theme. In that folder, create a workflows
folder. Inside there, create a YAML file. You can call it whatever you want. Mine's currently called deploy-theme.yml
.
Paste this juicy deploy theme recipe:
name: Deploy Theme
on:
push:
branches:
- master
- main
jobs:
deploy:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Deploy Ghost Theme
uses: TryGhost/action-deploy-theme@v1
with:
api-url: ${{ secrets.GHOST_ADMIN_API_URL }}
api-key: ${{ secrets.GHOST_ADMIN_API_KEY }}
Remember to configure the secrets
in your GitHub settings. The data for said secrets will come from setting up a custom integration in Ghost Admin. See the Action page for full installation instructions.
What's excellent about GitHub Actions is that they are infinitely customizable. I had two other things I wanted to do with my Smart theme in addition to automatically deploying it.
First, I wanted to build the theme's assets—JavaScript and CSS—before deploying. This ensures that I'm always working with a fresh build, and it helps keep my git log cleaner because I don't have to sync my build folder.
It was fairly trivial to implement. Under jobs
, I added the following lines:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '18.x'
cache: 'npm'
- run: npm ci
- run: npm run build
What this does is set up a Node action that installs the necessary dependencies using npm ci
or clean install
. Then, it runs npm run build
, which is the build script defined in package.json
. (That, in turn, runs rollup -c --environment BUILD:production
.)
With a fresh build of my theme's assets, the next task I wanted to accomplish was to deploy the theme, not just to a single site, but to three sites where I have the theme installed.
- name: Deploy mc site
uses: TryGhost/action-deploy-theme@v1
with:
api-url: ${{ secrets.GHOST_ADMIN_API_URL }}
api-key: ${{ secrets.GHOST_ADMIN_API_KEY }}
- name: Deploy demo site
uses: TryGhost/action-deploy-theme@v1
with:
api-url: ${{ secrets.GHOST_DEMO_URL }}
api-key: ${{ secrets.GHOST_DEMO_KEY }}
- name: Deploy my site
uses: TryGhost/action-deploy-theme@v1
with:
api-url: ${{ secrets.GHOST_ME_URL }}
api-key: ${{ secrets.GHOST_ME_KEY }}
Again, not too difficult. I set up additional secrets for each site and then run the Ghost deploy action for each site. It works perfectly and is a huge quality of life-improvement. Imagine the alternative: building assets, zipping up the theme, and then logging into each site and manually uploading the file 🤮
Here's the whole YAML file:
# Learn more → https://github.com/TryGhost/action-deploy-theme#getting-started
name: Deploy Theme
on:
push:
branches:
- master
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '18.x'
cache: 'npm'
- run: npm ci
- run: npm run build
- name: Deploy mc site
uses: TryGhost/action-deploy-theme@v1
with:
api-url: ${{ secrets.GHOST_ADMIN_API_URL }}
api-key: ${{ secrets.GHOST_ADMIN_API_KEY }}
- name: Deploy demo site
uses: TryGhost/action-deploy-theme@v1
with:
api-url: ${{ secrets.GHOST_DEMO_URL }}
api-key: ${{ secrets.GHOST_DEMO_KEY }}
- name: Deploy my site
uses: TryGhost/action-deploy-theme@v1
with:
api-url: ${{ secrets.GHOST_ME_URL }}
api-key: ${{ secrets.GHOST_ME_KEY }}
Now I just push the changes, sit back with a beer, and let the machines take the wheel.