Featured image thumbnail for post 'Self-hosting Netlify CMS without Netlify'

Self-hosting Netlify CMS without Netlify

The Netlify CMS project was built with the Netlify platform in mind. How can we host it ourselves as the ultimate headless blog CMS?

Joey Miller • Last updated May 04, 2023

Why Netlify CMS?

Netlify CMS is a headless CMS that makes it easy to write and edit content (e.g. markdown files) stored in a Git repository. It is easy to configure - you only need to update a YAML config file. It is a great choice when wanting to add content to any site built with a static site generator such as Next.js, Gatsby, Hugo, etc.

As the project name suggests, the project was built with the Netlify platform in mind. Hosting Netlify CMS without Netlify (i.e. with AWS) means you cannot use their Netify Identification Service to authenticate with your git repository providers, such as Github or Gitlab. This means we need to host our own external OAuth server to use Netlify CMS.

To simplify the process, I developed a single Docker image that provides both a standalone Netify CMS installation and an OAuth server.

Setting up Netlify CMS with Docker

In this post, I will be assuming that you are connecting Netlify CMS to a GitHub repo.

To get started create a docker-compose.yml file with the following:

version: '3'

        # headless netlify cms service on port 80
        image: itsmejoeeey/docker-netlify-cms-standalone:latest
        restart: always
            - ORIGIN=<your root url>
            - OAUTH_CLIENT_ID=<your github client id>
            - OAUTH_CLIENT_SECRET=<your gitlab client secret>
            - ./config.yml:/app/config.yml

When authenticating with Github, create your OAuth secrets by going to Settings > Developer settings > OAuth Apps. Create a 'New OAuth App' with:

  • Homepage URL being <ORIGIN>
  • Authorization callback URL being <ORIGIN>/callback

To the same directory as the docker-compose.yml file, save this example config.yml file making sure to update:

  • repo, branch to match an existing Github repository you have access to. i.e.:
    repo: itsmejoeeey/test-blog-content
    branch: main
  • base_url to match the <ORIGIN> environment variable you set above. i.e.:
    base_url: https://cms.example.com

From the same directory as the docker-compose.yml file, start the container with:

docker-compose up -d

For more detailed setup instructions, view the README.

Example blog content repository structure

My default example config.yml file organizes the repo as follows.

├── posts
│   └── <year>
│       └── <month>
│           └── example_post.md
└── static
    └── uploads
        └── document.pdf
    └── media
        └── blog
            └── <year>
                └── <month>
                    └── example_post
                        └── image_1.png
                        └── image_2.png

When you use Netlify CMS to create a post, the markdown files will be stored under posts. Blog media such as images for a single post are grouped (by markdown filename) and stored in a similar structure under static/media.

I chose to keep the static media (such as documents and blog post images) separate from the markdown files so that they could be easily hosted outside of Git if necessary at a later date.

While this structure is great for my needs, it won't be perfect for everyone. Netlify CMS is extremely flexible and configurable, so you'll be able to modify this to meet your needs in no time.

Hosting your blog content repository with Gatsby

This blog you are reading is made with Netlify CMS + Gatsby (the React-based static site generator). Gatsby is used to host the blog markdown files as a navigable website.

The Gatsby starter blog project is a good place to start, but some modifications are necessary for it to be usable with Netlify CMS as we set up earlier.

First, we need to clone the starter project and install the necessary dependencies:

git clone [email protected]:gatsbyjs/gatsby-starter-blog.git
cd ./gatsby-starter-blog
npm install
rm ./content # delete the old content

Then we need to install the gatsby-source-git plugin, as this will pull the markdown files from our blog content repository into the Gatsby graph.

npm install gatsby-source-git

And configure the plugin in gatsby-config.js by replacing:

      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/content/blog`,
        name: `blog`,


      resolve: "gatsby-source-git",
      options: {
        name: "blog-content",
        remote: `https://<your github username>:<your github token>@github.com/itsmejoeeey/joeeey-site-blog-content.git`,
        branch: "master",
        patterns: [`posts/**`, `static/media/**`],

Don't forget to insert your GitHub credentials. Go to Settings > Developer settings > Personal access tokens > Fine-grained tokens and create a token with the Contents and Metadata repository permissions enabled for your blog content repo.

We are now ready to start the development server with npm run develop. Navigate to localhost:8000 and you will see the blog posts in your repo listed on the root page.

This is a basic starting point - numerous markdown fields from my Netlify CMS example config file are unused (such as cover, tags, etc).

The gatsby-starter-netlify-cms project achieves a similar outcome and is worth a look. It has some additional features like generating tag pages automatically. Getting it up and running is out-of-scope for this post, as additional modifications would be necessary; we are now hosting Netlify CMS and our blog content repository externally (rather than having it all self-contained in the Gatsby repo).

If you found this post helpful, please share it around: