Bucket of Code

How To Run Github Pages Locally With Docker

Combine Docker and Jekyll to test your GitHub Page site locally

Here is a quick and easy way to test a Github Pages site locally using the Jekyll Docker image for Github Pages. This allows you to see and test your changes to your Github Pages site before pushing them to Github. This solution only requires Docker to be installed and does not require you to install Ruby to run Jekyll. Docker is a pretty common technology used these days to be able to run applications locally. And as a developer, you are more likely to have Docker than Ruby installed on your machine…unless, of course, you are a Ruby developer.


That’s it!

This approach doesn’t require Ruby or Jekyll. It doesn’t even require a GitHub repository for your site. However, it would be nice to have a known, working site on Github Pages, so that you reduce the amount of troubleshooting you may need to do to get this solution working.

A Basic docker-compose File

In its simplest form, here is a docker-compose.yml file to get you started.

# docker-compose.yml
version: "3.9"
    image: jekyll/jekyll:pages
    command: jekyll serve
      - 4000
      - .:/srv/jekyll

Using this docker-compose.yml file will:

This file should be enough to get you started. Now you just have to run docker-compose up and visit your site in a browser at http://localhost:4000.

A More Advanced docker-compose File

Whenever I start a new Github Pages site, I always start with the docker-compose.yml file below. It’s based off of the file above, but it adds a few more features that I was looking for while writing content.

version: "3.9"
    image: jekyll/jekyll:pages
    command: >
      jekyll serve \
      --watch \  # Enable auto-regeneration of the site when files are modified.
      --force_polling \  # Force watch to use polling
      --drafts \  # Process and render draft posts
      --unpublished \  # Render posts that were marked as unpublished
      --future \  # Publish posts or collection documents with a future date
      --verbose \ # Print verbose output
      --trace \  # Show the full backtrace when an error occurs
      --strict_front_matter \  # Cause a build to fail if there is a YAML syntax error in a page's front matter
      -d /tmp/_site  # Change the directory where Jekyll will write files
      - 4001:4000  # Serves the site on port 4001. Change this if you have other apps running on that port
    # A simple healthcheck to tell Docker that the site is up and running
      test: ["CMD", "curl", "-f", "http://localhost:4000"]
      interval: 1m30s
      timeout: 10s
      retries: 3
      - .:/srv/jekyll  # Mount the project into the pwd of the Docker container

I’ve commented on each line of the file to show what each does. In a nutshell, this more advanced docker-compose file will:


This method isn’t 100% foolproof and does have some issues. Here are some FAQs that I think might be useful.

Why are my changes not reflected on my site, when I add a new page or make changes to my content?

The --watch option does its best to determine when relevant files have changed. However, Jekyll’s internal cache sometimes gets in the way. The cleanest way to remove the cache is to recreate the Docker container.

Recreate the Docker container

  1. ctrl-c (on Windows/Linux) to shut down the container when it is running. If it hangs, try ctrl-c again to force it to shut down. If this still doesn’t work, try restarting the Docker service.
  2. Run docker-compose down this will stop the container if it’s still running and delete the container completely
  3. Run docker-compose up to recreate the contatiner.

Why are my changes not reflected on my site, when I make changes to my _config.yml file?

Unfortunately, the _config.yml is excluded from the --watch flag. The only way to re-read the config file is to stop and restart the container. If this still doesn’t work, try recreating the container with the instructions above.

When I visit the site in my browser, the site cannot be reached.

The default port for Jekyll is port 4000 and the output in the console shows for the URL. However, that port is is exposed only inside the container. The basic docker-compose file above uses port 4000, but the advanced file exposes the site on port 4001.

Here are a few things to check: