npm has recently unleashed private modules to world. This allows you to publish a node module to your own private registry, and install it via
npm install as normal. I think this is a good move from npm, and with a price of only $7/user/month, it doesn’t break the bank.
If you are already using GitHub to host your private module’s source code, you might however be wondering whether paying for another service is worth it. In some situations, using a private npm registry is the correct choice, such as if you need strict semver adherence, or need to separate your source code hosting from your production module hosting. In many cases, however, you can get away without using npm for hosting your private modules.
In this article, I’m going to show you how you can replicate npm private hosting using GitHub or Bitbucket, and access these modules from your deployment server and/or Heroku.
The basic idea is as follows:
- Host your module’s source code on GitHub/Bitbucket, in a private repo.
- Define a dependency on this module in your project’s
package.json, pointing to the private module with an SSH url.
- Setup a deploy keypair to access your private module via SSH, and upload the public key to GitHub/Bitbucket.
- Add a
postinstallscript to your project’s package.json, to setup and teardown SSH configuration, in order to access the private module during
- Encode the private key and set as an environment variable during deployment.
The code for an example project based on the below can be found on GitHub.
1. Hosting your module
The major difference between the two is their pricing structure. GitHub requires you to pay for any private repos, whereas Bitbucket allows you to host private repos for free. Many people prefer GitHub to Bitbucket, but the latter is still an excellent choice, and is seriously worth considering.
You can choose either service to host your module – both work just fine with this technique. If you don’t know which one to pick, start with Bitbucket – it’s free.
Note: If you pick GitHub, try to ensure you sign up as an organization. This is so that you can create a machine user and add it to the repo as a read-only collaborator. If you use a personal plan, you’ll be stuck with a deploy key with read/write permissions, which is less than ideal – a deployment should only ever need read-only access to the private module.
2. Depending on your module
Modules are marked as dependencies with
Let’s assume you have a project which needs to access code from a private module,
private-parts, which is hosted on Bitbucket under the user
bigbluebananas. Setup your package.json file to resemble the following:
1 2 3 4 5 6 7
Since this repo is private, when we try to run
npm install, we’ll be greeted with an error. To access it, we’ll need to setup a deploy key.
3. Setup a deploy key
Create a new SSH key:
- Replace the email address with something real. Or not, it doesn’t really matter.
- When prompted with a location to save the generated keys, don’t overwrite your existing SSH key!! Save your key to somewhere else, e.g. your desktop.
- Don’t add a passphrase to the key.
You should now have a private/public keypair. You’ll use the private key on your deploy server, and upload your public key to GitHub/Bitbucket – thus providing deploy-time access to your private module.
The next step is to upload the public key.
You’ve got two options with GitHub.
If you have a personal account, or you aren’t fussed about your deploy key having read/write privileges, use a GitHub Deploy Key. They are easier to setup than machine users.
If you have a organization account, and want to limit your deploy key to read-only access, setup a GitHub Machine User and add it as a collaborator to your repo.
Deploy keys in Bitbucket provide read-only access to the repo. Check out this guide for details on how to add a deploy key.
4. Add pre/postinstall scripts
To access the private module, you need to let npm know that you’ve got a private key, and that it should use this private key for SSH connections to your host.
Unfortunately, there isn’t an explicit hook into npm to achieve this. Instead, we are going to setup a temporary SSH configuration before npm installs the module, and tear it down afterwards. We achieve this using
Firstly, grab the scripts from this gist. Copy these into your project’s root folder.
Make sure the scripts are executable:
Next, add the following entries to your
1 2 3 4 5 6 7 8
The preinstall script sets up an SSH config file, containing the correct config to connect to GitHub/Bitbucket, with the private key harvested from an environment variable.
The postinstall script undoes this action, and clears out the environment variable.
This way, when
npm install runs, and it encounters the SSH urls in the
dependencies section of
package.json, it will automatically use the temporary SSH config, authenticating with the private key and pulling down the private module.
5. Adding private key to environment
The last thing to do is to set the private key in the environment.
The key should be base64 encoded. At the terminal, use
deploy_key with the name of your key.
This should produce a long string of characters. Set this as the environment variable
GIT_SSH_KEY on your deployment server, and then try running
npm install. Your private module should be pulled down correctly.
Bonus: Heroku integration
This technique was built to work with Heroku. Simply set the
GIT_SSH_KEY environment variable on your Heroku app and push your project’s code to Heroku. The container should use the private key from the environment variable to pull down your private module.
Bonus 2: Rudimentary versioning
You can use git tags to achieve basic package versioning. Simply tag your private module and depend on this in your
1 2 3 4 5 6 7
You don’t get semver niceness like you do with npm packages, but for basic versioning, git tags might be all you need.
You don’t need to go through the extra cost and hassle associated with npm private registries to deploy private modules. This technique shows you a way to host and deploy private modules, which works especially nicely when using Heroku.
Credit goes to this Stack Overflow answer from Michael Lang, which inspired this technique.