Using GitHub as an 11ty blog that auto-deploys to Dreamhost
29 January 2023
For all of October and half of November, I was on the road, driving the entirety of the historic Lincoln Highway! My plan was to blog as I went, typing up my day when I stopped for the night. I soon learned that was an overly ambitious goal, and every night I was so full from my day that I could do nothing more than watch a movie or do a little journaling in my notebook. In Evanston WY, I was finally able to finish seaming my sweater!
Even though the blogging didn’t get done on the trip, I was able to set up my site so that I could blog from the road. I didn’t want to bring my MacBook Pro or my Linux laptop with me, and packed my Chromebook instead. I didn’t need all of the computing power from my work or personal computers, since I really only wanted to do things like check email, review my map, or make a hotel reservation.
I also fell in love with 11ty, a super customizable static site generator. A WordPress blog would have been the easy option, with its web-based editor, and although I do enjoy working with WordPress very much, I wanted the advantages of a static site. 11ty is so much fun!
For this post, I’m not going too deep into setting up an 11ty blog — that could be a whole series of posts itself — but I’ll explain a little bit about how my blog is set up before diving into it.
Building an 11ty blog
The source code for my road trip blog is available on GitHub if you’d like to view everything. There are still some portions I’d like to refactor; I’d gotten used to doing things in Twig, and there’s a bit of an adjustment moving to Nunjucks.
One of the first things I did was add an empty .nojekyll
file to the root of my project. GitHub Pages by default uses Jekyll to build static sites, and even though I am not using GitHub Pages to serve my site, I’d much rather have it in there. Just in case.
I then set up my package.json
and ran npm install @11ty/eleventy --save-dev
to install 11ty into my project. As I mentioned, there is a lot that could be written about setting up an 11ty site, and the documentation is extensive! 11ty is so customizable, with scripts and plugins configured in .eleventy.js
, but for this post, the most relevant lines are for the input and output directories.
module.exports = function( eleventyConfig ) {
return {
dir: {
input: "source",
output: "site"
}
};
};
The input
and output
directories are relative to the project root. 11ty will look in the input
directory for the files it needs to compile the site. I keep all my source files — templates, macros, data, scripts, styles, etc. — in the source
directory. The output
directory is the folder where the built site lives; I cleverly and originally called this site
.
In my package.json
, I have commands in the scripts
section for running 11ty (along with other scripts) to compile the site:
{
"scripts": {
"build": "npx eleventy"
}
}
Because I use GitHub Actions to build my site, I don’t commit any of the build in the repository, and include the site
directory in a .gitignore
file, since actions will do all that work for me.
(If you don’t already, it’s also a good idea to add node_modules
to your .gitignore
too!)
Creating GitHub Actions to build the site
Although there are now built-in Actions for running GitHub Pages, this is not quite what I need for this project. I found peaceiris’ GitHub Pages Action to be a quick-to-setup workflow for building my 11ty blog. The basic workflow is this:
- When a pull request is merged into the
main
branch, run thebuild-11ty
workflow. - Then, it sets up Node.js and runs
npm install
based on the packages inpackage.json
file. - Next, it runs
npm run build
to compile everything for the blog to the/site/
directory. - This directory is pushed to a separate branch called
production
, which is only ever used to contain the built site.
For this, we create a workflow file in a workflows directory specific to GitHub: /.github/workflows/build-11ty.yml
. The workflow has a name
that describes the workflow, an on
parameter to tell the workflow when to run, and a jobs
parameter that specifies what commands will run in the workflow.
GitHub hosts thorough documentation for its Actions. As an example, the full workflow in my repository is below:
name: Build 11ty
on:
pull_request:
types:
- closed
branches:
- 'main'
jobs:
if_merged:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v3
- name: Setup node.js
uses: actions/setup-node@v3
with:
node-version: latest
- name: Install and build
run: |
npm install
npm run build
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
publish_dir: site
publish_branch: production
github_token: ${{ secrets.GITHUB_TOKEN }}
GitHub Actions have really detailed options, but the workflow to build an 11ty site does not need to be overly complex. We only want to run this action when a pull request is merged into main
, and we only need the production
branch to hold the built site. Actually deploying the site happens in another process.
In the workflow step that actually runs peaceiris’ action, there is a parameter for secrets.GITHUB_TOKEN
. This token doesn’t need to be defined anywhere in the workflow or in the project repository! From peaceiris’ documentation:
For newbies of GitHub Actions: Note that the
GITHUB_TOKEN
is NOT a personal access token. A GitHub Actions runner automatically creates aGITHUB_TOKEN
secret to authenticate in your workflow. So, you can start to deploy immediately without any configuration.
So now, the main
branch only includes the source for building the site, the /source/
directory that has all the blog files (templates, CSS, etc) and the project files (package information, 11ty configuration, etc), and the production
branch only includes the actual built site (HTML pages, images, compiled stylesheets, etc).
n.b. Because the production
branch only holds the built site, it will always be some number of commits ahead and behind main
. You can check out my production
branch at GitHub.
Deploying the built site to Dreamhost
Here’s the part that deploys the built site to the Dreamhost webserver. For this, we use Marko Marković’s Simple PHP git Deploy script. The file is heavily commented and gives really good instructions for configuring the script for your website setup.
There are a few requirements:
git
andrsync
are required on the server that’s running the script (server machine).- The system user running PHP (e.g.
www-data
) needs to have the necessary access permissions for theTMP_DIR
andTARGET_DIR
locations on the server machine.- If the Git repository you wish to deploy is private, the system user running PHP also needs to have the right SSH keys to access the remote repository.
For the first two items, these are included with all Dreamhost plans. I didn’t have to change anything for those. So far, I have not tried this method with a private repository, so I cannot speak for the third item.
The initial step is to create a directory that will be uploaded to your site, named something like deployment
or scripts
, or really anything else! This folder is not something that will be committed to the repository, because it’s about to hold some secrets.
The next step is to visit the deploy script repository, and download two files: deploy.php
and deploy-config.example.php
into this new deployment folder. These are the scripts that help work the magic! Rename the example file to deploy-config.php
and configure it as in the README
. We’ll go over some of the most important options.
The first configuration setting is the SECRET_ACCESS_TOKEN
. This can be any type of password, phrase, etc. This is the main reason these files should not be committed to the project repository! It’s going to be used in a GitHub webhook to give access for deploying the site.
Next is the REMOTE_REPOSITORY
, and is the .git
address of the repo. For example, mine is https://github.com/reedcodes/lincolnhighway.git
.
The BRANCH
is the one specified above, in the GitHub action, that will only hold the built files for the website, e.g. production
. This branch must already exist in the repository, but it’s not necessary to have anything there yet.
The TARGET_DIR
is the directory where the built site lives on the web server. For Dreamhost, a website’s home at the site root is /home/[USERNAME]/[DOMAIN.COM]/
, where [USERNAME]
is the name of the account, and [DOMAIN.COM]
is the site’s domain. For example, on my Lincoln Highway blog, my target directory is /home/reedcodes/lincolnhighwayjournal.com/
. Anything under this directory will be the website!
DELETE_FILES
can either be false
so anything from the repository will be copied over without cleaning out any files from the server, or true
to start over with a clean slate before copying files over. Most of the time, this should be set to false
, but if the website is being redesigned, filenames changing, etc. then true
is the way to go.
If there are any files that need to be excluded from the production branch, so they are not copied into the website, they are listed as an array for the EXCLUDE
option. Since only the built site is in the production branch, this could be left empty. However, keep in mind that this option only excludes, and cannot set any explicit includes.
Now let’s set up the webhook!
Adding the webhook in the GitHub repository
Remember above where we set up the SECRET_ACCESS_TOKEN
? Now is the time to use it. In order for the deployment script to know when to run, GitHub needs to send notification. Here, we add a webhook for just that.
In your GitHub repository, go to the Settings page, and in the menu select Webhooks. We are going to add a new webhook using the link to the deployment script and the SECRET_ACCESS_TOKEN
defined in the configuration. This link points to deploy.php
and not the configuration file, with a query string using the secret access token. For example, if your directory is something like deployment
, your webhook link would look something like this:
https://mysupercoolwebsite.com/deployment/deploy.php?sat=EXTREMEMLY_SECRET_TOKEN
This link is entered in the field for Payload URL. Use the default option for Content Type, and it’s recommended to leave the option for SSL selected.
Defining webhook triggeres is optional, but for a script like this, where you only want to push the “ready to publish” website, I recommend selecting Pull Request and Push events. The GitHub Action kicks off when a pull request is merged into main
, immediately building the website and pushing its contents to production
. Then the webhook can send the notification to the deployment script, so it can grab the updated version of the website!
Finishing up to get ready for blogging
Phew! We made it! We created our 11ty blog and added a GitHub repository for it; created a .yaml
file for setting up a GitHub Action; configured a deployment script that can grab the built website; and added a webhook to GitHub that will notify the deployment script to run.
From here, you don’t need to be at your regular computer to blog; blogs can now be written on a device that doesn’t have a whole development suite set up, such as a Chromebook (like I use) or an iPad. What I do is create a new branch using the GitHub web interface, and open a new pull request on that branch. Then I can add my new blog post file right there, work on it until I feel ready, then review and merge the pull request. A moment or two later, updated website!
Final thoughts
As I mentioned before, I didn’t get to do as much blogging from the road as I expected, since I grossly underestimated how tiring it would be to drive five to eight hours per day (who knew, right?). I am still so happy that I was able to do this anyway, so the capability was there, and also ready for the next big adventure.
If you have any questions or comments, or notice something wrong or missing, please feel free to send me an email or open an issue in my repository.