Motivation
This website has been online for five years, consistently using the Hugo framework. Hugo is an excellent framework, but after years of use, I’ve encountered some inconveniences.
Specifically, since my website is deployed using Docker containers and the blog content is managed through a GitHub repository, every time I need to update the blog content, I have to modify markdown files on my local computer, commit to the GitHub repository, and then pull the updates on the server, rebuild the container, and restart it.
While this workflow isn’t terribly complicated, the main drawback is that I must have the blog’s Git repository on the computer I’m using to write. Sometimes I get inspiration and want to write something, but if the blog’s Git repository isn’t available on my phone or current computer, I have to write it elsewhere and copy it into the blog’s markdown files later. This is somewhat inconvenient, but what’s more frustrating is when I want to continue writing a draft blog post—if I don’t have the Git repository available, I can’t see the previous content and thus can’t continue writing.
So I started thinking: is there a way to add an editing system to the blog that allows me to save drafts on the server? This way, from any computer, or even from my phone, I could open the draft and continue writing, and then publish it to the blog when finished.
Following ChatGPT’s suggestions, such a system is called a Content Management System (CMS), and it recommended using DeCap CMS, an open-source CMS system to build the blog’s editing system.
Introduction to DeCap CMS
DeCap CMS is a Git-based content management system that supports multiple static website generators (such as Hugo, Jekyll, Gatsby, etc.). It provides a user-friendly interface that allows users to edit and manage blog content through a browser and commit changes directly to a Git repository.
Installing DeCap CMS is quite simple—you just need to place an admin folder in the root directory of the static website with an HTML file and a configuration file inside. The more complex part is configuring a backend service that communicates between DeCap CMS and the GitHub repository.
How DeCap CMS Works
DeCap CMS Workflow
The DeCap CMS workflow can be divided into roughly 4 steps:
- Admin Login Interface: By placing an
adminfolder in the root directory of the static website, you can access the DeCap CMS login interface by visitinghttps://blog.example.com/admin/. - User Authentication: DeCap CMS supports multiple authentication methods, including GitHub OAuth, GitLab OAuth, Bitbucket OAuth, and others. Users can choose the authentication method that suits them to log in.
- Content Editing: After successful login, users can edit blog content through the DeCap CMS interface. DeCap CMS provides a WYSIWYG (What You See Is What You Get) editor, allowing users to edit blog content directly in the browser and preview the results.
- Content Publishing: When users finish editing, they can click the publish button. Using the access token we provide to DeCap CMS to access the Git repository, DeCap CMS will commit the changes to the Git repository, trigger the static website generator’s (like Hugo) build process, and finally publish the updated blog content to the website.
Components of DeCap CMS
From this, we can see that we need to prepare 4 things:
- DeCap CMS Frontend Files: You need to create an
adminfolder in the blog’s root directory and place DeCap CMS frontend files—an HTML file and a configuration file—inside. - Login Authentication: You need to choose an authentication method and configure the corresponding authentication information.
- Git Repository Access Token: You need to generate a token that can access the blog’s Git repository and configure it in DeCap CMS.
- Proxy Service to Forward DeCap CMS Requests to the Backend: You need to configure a backend service to handle DeCap CMS requests and forward them to the Git repository.
In my case, I use Hugo as the blog framework and the blog content is stored in a GitHub repository, so I need to configure DeCap CMS to support Hugo. Since DeCap CMS supports GitHub OAuth authentication, I can choose to use GitHub OAuth for user authentication. Finally, I need to configure a backend service to handle DeCap CMS requests and forward them to the GitHub repository.
Installing and Configuring DeCap CMS
Installing DeCap CMS Frontend Files
For the Hugo framework, DeCap CMS requires frontend files to be placed in the static folder of the Hugo project’s root directory (for other frameworks, they might need to be placed in public, src, site, etc., depending on DeCap CMS’s documentation). Therefore, we create a static/admin folder in the blog’s Hugo project root directory and place the DeCap CMS frontend files:
-
index.html: The main interface file for DeCap CMS, containing the frontend logic and interface design.1 2 3 4 5 6 7 8 9 10 11<!doctype html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Jin Li Misc Admin</title> </head> <body> <script src="https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"></script> </body> </html> -
config.yml: The configuration file for DeCap CMS, used to configure the authentication method, Git repository access tokens, and other information.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27backend: name: github repo: jin-li/blog branch: main base_url: https://decap.example.com auth_endpoint: /auth media_folder: "hugosite/static/images" public_folder: "/images" site_url: https://blog.example.com display_url: https://blog.example.com publish_mode: editorial_workflow collections: - name: "blog" label: "Blog Posts" folder: "blog/content/posts" create: true slug: "{{year}}-{{month}}-{{day}}-{{slug}}" fields: - { label: "Title", name: "title", widget: "string" } - { label: "Date", name: "date", widget: "datetime" } - { label: "Draft", name: "draft", widget: "boolean", default: true } - { label: "Tags", name: "tags", widget: "list", required: false } - { label: "Body", name: "body", widget: "markdown" }The
backendsection configures the backend service to use GitHub with the repositoryjin-li/blogon themainbranch, and the backend service address ishttps://decap.example.comwith the authentication endpoint at/auth. Themedia_folderandpublic_folderconfigure the storage and access paths for media files respectively. Thesite_urlanddisplay_urlconfigure the website URLs. Thepublish_modeconfigures the publishing mode, using editorial workflow here. Finally, thecollectionssection configures content collections, with one “blog” collection configured for managing blog articles.In this way, when we visit
http://yourwebsite.com/admin/, DeCap CMS will load the admin interface and display the GitHub OAuth login option.
Configuring GitHub OAuth Authentication
To use GitHub OAuth authentication, we need to create an OAuth application on GitHub and obtain the corresponding Client ID and Client Secret. The specific steps are:
- Log in to GitHub, click the avatar in the top right corner, and select “Settings”.
- Select “Developer settings” from the left menu.
- Select “OAuth Apps” from the left menu, then click “New OAuth App”.
- On the “Register a new OAuth application” page, fill in the application name, homepage, and callback URL. The homepage URL should be your blog’s address (for example,
https://blog.example.com), and the callback URL should be the authentication endpoint address of the DeCap CMS backend service (for example,https://decap.example.com/callback). - Click the “Register application” button to complete the application registration. After registration, you’ll see the application’s Client ID and Client Secret. Record these two values; you’ll need them when configuring DeCap CMS later.
Configuring DeCap CMS Proxy
When we visit https://blog.example.com/admin/ and click the GitHub OAuth login button, DeCap CMS will send an authentication request to https://decap.example.com/auth. This request needs to be forwarded to GitHub’s OAuth authentication endpoint to complete the authentication flow. Therefore, we need to configure a proxy to handle this request.
There are Docker containers developed by community members available to handle DeCap CMS request forwarding, such as:
I attempted to deploy both containers, using Traefik and Cloudflare Tunnel for reverse proxying like other containers, but neither were successful.
Eventually, I discovered that someone had implemented this proxy functionality using Cloudflare Workers, as shown in the GitHub repository decap-proxy. Cloudflare Workers is a serverless computing platform provided by Cloudflare that allows us to run JavaScript code on Cloudflare’s edge network, thereby implementing request processing and forwarding.
The specific steps can be found in the GitHub repository’s documentation. The general process is as follows:
-
Clone the
decap-proxyrepository to your local machine:1git clone https://github.com/sterlingwes/decap-proxy.git -
Enter the
decap-proxydirectory and find awrangler.toml.samplefile, which is the configuration file for Cloudflare Workers. We need to copy it and rename it towrangler.toml:1cp wrangler.toml.sample wrangler.toml -
Edit the
wrangler.tomlfile to configure the relevant Cloudflare Workers information, for example:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34#:schema node_modules/wrangler/config-schema.json # "compatibility_date" and "main" are values you are unlikely to need to change compatibility_date = "2025-11-17" # schema version main = "src/index.ts" # entry point for the worker # The "name" parameter defines the name of the worker in your Cloudflare # Dashboard. It also specifies the first element of the default URL that # will reach your worker. name = "decap-proxy" # optional: uncomment and alter the following lines if using a custom domain. # route = { pattern = "decap.example.com", zone_name = "example.com", custom_domain = true } # optional: uncomment the following line if you don't want wrangler to set up # the worker to be available at the default workers.dev url of # <worker-name>.<account-name>.workers.dev # where <worker-name> is the "name" parameter configured above. # workers_dev = false # optional: this worker template uses Web Crypto API natively and doesn't require # nodejs_compat you can add this flag if you need Node.js polyfills, # but please be aware that those polyfills may have vulnerabilities # compatibility_flags = ["nodejs_compat"] # Variable bindings. These are arbitrary, plaintext strings (similar to environment variables) # Docs: # - https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables # Note: Use secrets to store sensitive data. # - https://developers.cloudflare.com/workers/configuration/secrets/ [vars] GITHUB_REPO_PRIVATE = "1" # Should be set to "1", if your website repo is privateThe comments in the file explain things clearly. Mainly, we need to modify the
routeto configure it to the address of our DeCap CMS backend service, for example,decap.example.com. If you don’t want Cloudflare to automatically set up the defaultworkers.devdomain, you can setworkers_devtofalse. Finally, we need to configure a variableGITHUB_REPO_PRIVATEin the[vars]section; if your blog’s GitHub repository is private, set it to1, otherwise you can leave it unset. -
After configuring the
wrangler.tomlfile, we can use the Cloudflare Workers CLI toolwranglerto deploy our Worker. The GitHub repository mentions that you can set your Cloudflare account and token as environment variables to log in directly from the command line, but I tried this without success. So I used theunsetcommand to clear the environment variables, then ran thenpx wrangler logincommand to log in.If you haven’t installed
wranglerbefore, the first time you runnpx wrangler login, the system will prompt you to installwrangler. You can follow the prompts to install it. After installation is complete, runnpx wrangler loginagain, and it will display a URL in the command line, prompting you to open this URL to complete the login.Note: You need to open this URL on the same computer where you ran the
npx wrangler logincommand, because after you complete verification with your Cloudflare account, the URL thatwranglerredirects to is a local address, such ashttp://localhost:8787/callback?code=xxx&state=yyy. This URL will be intercepted bywrangler, which will extract authentication information from it to complete the login. If you open this URL on another computer, although the verification completes,wranglercannot intercept the URL, so the login will fail.After successful verification, you’ll see the following browser interface:

-
After completion of verification,
wranglerwill indicate successful login. Next, we set the GitHub OAuth application’s Client ID and Client Secret we obtained earlier as environment variables for the Cloudflare Worker:1 2npx wrangler secret put GITHUB_CLIENT_ID npx wrangler secret put GITHUB_CLIENT_SECRETAfter running the above commands, the system will prompt you to enter the corresponding values. After entering them,
wranglerwill store these values as secret environment variables in Cloudflare. -
Finally, we can deploy our Worker:
1npx wrangler publish -
After deployment is complete, you can open a browser and visit
https://decap.example.com. If you see the page displayingHello 👋, it means our Cloudflare Worker has been successfully deployed and is running.
Testing DeCap CMS Access
After completing the above installation and configuration, our DeCap CMS should be ready to work normally. We can test accessing the DeCap CMS login interface by visiting https://blog.example.com/admin/:

If you can see the GitHub OAuth login option and successfully log in, you’ll see the DeCap CMS content editing interface:

In the editing interface, we can create new blog articles, edit existing blog articles, and preview the editing effects. When we finish editing, we click the publish button, and DeCap CMS will commit the changes to the GitHub repository, trigger Hugo’s build process, and finally publish the updated blog content to the website.