Arial view of a desktop, with an open Chromebook in the middle. A cup of tea is on the left of the Chromebook. On the right is a small blue notebook and a compact two-button mouse. Underneath is a desk mat, a rainbow pastel gradient with stars on it.

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:

  1. When a pull request is merged into the main branch, run the build-11ty workflow.
  2. Then, it sets up Node.js and runs npm install based on the packages in package.json file.
  3. Next, it runs npm run build to compile everything for the blog to the /site/ directory.
  4. 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

      - closed
      - 'main'

    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
      contents: write
      - uses: actions/checkout@v3

      - name: Setup node.js
        uses: actions/setup-node@v3
          node-version: latest

      - name: Install and build
        run: |
          npm install
          npm run build

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
          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 a GITHUB_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 and rsync 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 the TMP_DIR and TARGET_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

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/ 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:

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.