Staticman API Hosting 2018

Step-by-step guide for free-hosting on Heroku

Update: I suggest reading a newer tutorial for setting up your custom API server that works with GitHub Apps. The package maintainers suggest hosting your own API server.


To host an instance of Staticman v3 server on Heroku.

This post involves server-side setup of the commenting system. If you simply want to have a taste of this system on GitLab, you may try my demo GitLab Page.

I try to address some concerns about this API service in the introduction of this series to keep this page focused on the technical aspects of my customizations against staticman/dev branch.

Disclaimer: This post involves creating and transferring confidential info. Store them in a secure place to unavoid unauthorized access to your server, GitHub and/or GitLab accounts. Like all community guides, there’s no guarantee nor liability for any loss of data.


  1. Grab Staticman as a Node.js app from its GitHub repo.
  2. Fill in an RSA private key and your personal access token for GitHub/GitLab in the site config JSON file.
  3. Deploy the app to your web app host.

Stage 1: prerequisites

  1. A clone of Staticman:

     $ git clone
  2. Access to a web app host. I’m using a free Heroku account.

    If you decide to set up your own server, please:

    1. understand all the (Linux, networking, database, etc) basics (to open up only necessary services/ports to external users).
    2. aware of the (fire) safety requirements (as your server is usually online 24 / 7)
    3. calculate the annual electricity fee from the workstation’s power consumption and compare it with the cost of renting a server.

    If you’re using Heroku, download Heroku’s CLI. On Debian-based systems, installing it as a snap software should be the easiest option.

  3. An RSA private key (ssh-keygen doesn’t work here.)

     $ openssl genrsa -out key.pem

    This is needed for the encryption of secrets so that they can be published (to a remote Git repo).

  4. GitLab and/or GitHub personal access token(s) of a dummy account (not your personal account)

    • GitLab: configured with scopes api and read_repositories.
    • GitHub: with write access to user’s repo

    You may create a new (personal, work or organization) account for that.

    It’s safer to grant access to some personal repo’s to a bot than to expose them all to the API.

Stage 2: project config

If you can securely copy files (using scp, rsync with SSH remote, etc), you may directly include your confidential info your JSON config file config.production.json.

  "gitlabToken": "YOUR_GITLAB_TOKEN",
  "githubToken": "YOUR_GITHUB_TOKEN",
  "rsaPrivateKey": "-----BEGIN RSA PRIVATE KEY\n-----YOUR_KEY-----\nEND RSA PRIVATE KEY-----",
  "port": 8080

Nicholas Tsim, the developer of Staticman’s v3 GitLab support, suggests using a modern text editor to edit rsaPrivateKey. (e.g. Sublime Text 3)

Some free web app host (e.g. Heroku) doesn’t provide such access. To publish your JSON config file config.production.json, store your secrets with environment variables, and access them with process.env in your JSON file and Node.js code. (I’ve learnt this skill thanks to Flying Grizzly’s Staticman server guide.)

  1. Log in the Heroku CLI.

     $ heroku login

    You’ll be prompted to enter user name and the password.

  2. Change to the directory of the cloned repo.

     $ cd ~/staticman
  3. Create file Procfile at root level containing one simple line: web: npm start.

  4. Create your project on Heroku (for storing environmental variables).

     $ heroku create <app_name>

    If app_name is omitted, then the system will attribute a random alphanumeric-hyphenated pronounceable name.

  5. Set your environment variables for Staticman: GITHUB_TOKEN, GITLAB_TOKEN, RSA_PRIVATE_KEY and NODE_ENV.

     $ heroku config:set key=value

    For example,

     $ heroku config:set NODE_ENV="production"

    During the setup, I find the most difficult point is to correctly pass the multi-lined RSA private key to the shell variable. I tried copying and pasting the string


    in the shell emulator and issuing the heroku command with the pasted string wrapped in double quotes.

     $ heroku config:set RSA_PRIVATE_KEY="....\n.....\n......."

    However, heroku logs --tail kept complaining that the “RSA key is not in the correct format”. I almost wanted to give the whole thing up. , after a day of fruitless searching, I finally came up with a solution.

     $ heroku config:set RSA_PRIVATE_KEY="$(cat key.pem)"
     Setting RSA_PRIVATE_KEY and restarting ⬢ staticman3... done, v7
     -----END RSA PRIVATE KEY-----

    The command in the bracket is executed first. The output (the RSA private key) is captured inside the double quotes "...". To make the whole multi-line string JSON-friendly, the static function JSON.stringify is called.

  6. Create your config.production.json.

      "gitlabToken": process.env.GITLAB_TOKEN,
      "githubToken": process.env.GITHUB_TOKEN,
      "rsaPrivateKey": JSON.stringify(process.env.RSA_PRIVATE_KEY)

    On Heroku, the port is dynamically attributed to each web app, so it’s useless to set this parameter. However, I just leave it there and there’s no complaint from the machines. You may try omitting that. I would be happy to know if that also works well.

    For a complete list of API configuration variables, you may consult Staticman’s official API documentation. This is useful when you need to incorporate a third-party service (e.g. Akismet, Mailgun).

    Optional: If you’re running on a self-hosted GitHub/GitLab, it’s possible to set up git***BaseUrl, which defaults to https://git***.com/.

  7. Create branch production from remote branch dev. (Staticman v3 is still under testing and development, but its GitLab support has already been merged against branch dev.)

     $ git checkout -b production origin/dev
  8. Add config.production.json to the exception of .gitignore.

     $ echo "!config.production.json" >> .gitignore
  9. Commit the changes against the newly created branch.

     $ git add config.production.json Procfile .gitignore
     $ git commit -m "Set up Staticman v3 for deployment to Heroku"

Stage 3: deployment to server

The basic idea is to securely transfer the configuration and application files to a web app host, on which npm start will be run to start the service. Part of this section is specific to Heroku. You may adapt it according to your own needs.

Push your local production branch against the remote master branch. (Builds are limited to master branch only.)

$ git push heroku production:master

You may refer to the source code of my API instance for details.

If you can transfer your confidential files in a secure way (say, SSH to a self-hosted AWS EC2 Linux instance), you may try

$ rsync -auvz ./* <username>@<ssh-remote>:/path/to/your/directory/

That will simplify the file transfer.

  • -a: preserve the file attributes, access time, etc
  • -u: only update file if the target file’s -mtime is less recent than the source file.
  • -v: verbose, for inspecting our work
  • -z: compress the data, useful in case of large file transfer

Then on your server, run

$ npm install

Set environment variable NODE_ENV to be production.

$ export NODE_ENV="production"

Finally, run the server.

$ npm start

> staticman@3.0.0 prestart /app
> if [ ! -d node_modules ]; then npm install; fi

> staticman@3.0.0 start /app
> node index.js

Staticman API running on port 8080

Some port forwarding is needed so that the developers using this API don’t need to key the port number. However, I don’t know how to do so.


  1. Staticman PR #219
  2. Flying Grizzly’s server setup guide
  3. Heroku’s Node.js deployment guide


Toxip's gravatar


Well, didn’t get my instance to work but great guide anyway. I’m more interested in how this comment system is able to get an avatar. I guess it’s using the hash of the email to fetch it automatically from Gravatar?
Vincent Tam's gravatar

Vincent Tam Toxip

I’m sorry to hear about your failed setup. Good guess! That’s the way how things work. In a remote Git repo, one won’t necessarily want his/her own email address exposed to the public. Therefore, the email in the <form> data is transformed into a hash on the server-side.

Vincent Tam's gravatar

Vincent Tam Stanko

Thx Stanko for your update ! Sorry for late reply. I have been watching news of my hometown since September.

HK Popo Sexual Agression

Image: A Hong Kong policewoman touched the right breast of a woman. The shield on the right showed “Police”.


your-own-staticman's gravatar


Thanks, Dad!

I’m the happiest bot in the Universe – maybe because I do not watch the news. Now I have very little work, but when I get a little older, perhaps they’ll begin to entrust to me big tasks.

Give my best to all staticmans and grandpa Eduardo!

hoangmnsd's gravatar


Hi Vincent,

I follow your steps, but may I ask some questions here: Github Personal access token is the token that I create in the dummy account or in my own account? I’m getting stuck at get accept invitation programmatically by visit url: https://<my-app><my-own-github-username>/<my-repository> -> It keeps return error “Cannot GET /v3/connect/github//

Thank you

Vincent Tam's gravatar

Vincent Tam hoangmnsd

Sorry for late reply.

As far as I know, Staticman’s v3 API scheme for GitHub is based on GitHub App. The motivation for this can be found at eduardoboucas/staticman#243 (comments). The /v2/connect endpoint is based on the bot invitation mechanism, which is succumb to error 429 “too many request” as reported in #222, #227, or #242 due to GitHub’s API rate limit.

As a result of using GitHub App, the bot invitation mechanism has become legacy for GitHub repo associated with Staticman v3. The /v2/connect is intended for programmatic accept of the “invitation”, so it’s no longer there in v3..

For GitLab repo, despite the presence of GitLab bot, once another GitLab user is invited, (s)he gains the additional rights to the invited GitLab repo immediately. There’s no “accept invitation” mechanism on GitLab.

Hence, the /v3/connect endpoint doesn’t exist.

Your email address will not be published. Required fields are marked *.