Wiki
Clone wikibashinate / howto-nodeserver
How To: Provisioning a Node Application Server
This how-to will guide you through the process of provisioning a server that will able to host Node.js applications.
NOTE: My recommended configuration for running Node apps in production is behind nginx but that is out of scope for this how-to.
At the end of the guide you will have the following:
- A single server capable of hosting multiple node applications
- Git deploy endpoints (with custom
post-receive
hooks) that handle node application deployment.
Before you begin, you will need:
-
A barebones machine running Ubuntu 12.04 (or similar). I tend to fire one up using either Digital Ocean or Linode.
-
ssh
access to that machine. I prefer usingauthorized_keys
validated access (which Digital Ocean makes really easy, BTW) but it's up to you.
Let's get started. Log into the machine using ssh.
In this exercise I'm going to be running as the root
user, but if you aren't then remember to sudo
as required.
First, download the bashinate
BASH script and make it executable:
wget https://bitbucket.org/DamonOehlman/bashinate/raw/master/bashinate -O bashinate chmod a+x bashinate
Next, we are going to need to create a text file that will provide directives to bashinate (via STDIN
) as to what it needs to do.
Using your favourite text editor, create the following system_recipe
text file (lines starting with a #
are ignored by bashinate):
# tell bashinate that we are ok with an update to Upstart.conf allow-modify /etc/dbus-1/system.d/Upstart.conf # install node node 0.10.22 # set up a holding space for our new node app nodeapp helloworld 3060
NOTE: The need for the allow systemupdate
line is currently being investigated.
Run the script:
./bashinate < system_recipe
This sets bashinate in motion.
As it parses the incoming stream, it is looking for and what I've called "scriptlets". In our example above, we have used three scriplets:
-
the allow scriplet, which we use to tell bashinate core that certain operations are ok. In this case we are giving the thumbs up to the
patch
operation which in turn will allow us to patch upstart to allow user jobs. -
the node scriptlet which is used to install a localized version of node on the machine.
-
the nodeapp scriptlet which prepares a container for a node application on the machine.
These scriptlets in turn may invoke other scriptlets by using the require
function which is core to bashinate. For example, the nodeapp
scriptlet uses the githost scriptlet to bootstrap a git repository on the server.
After a time, provisioning will complete (bashinate tends to compile things like node from source), but there are still a few manual steps that need to be taken care of (automation is on the TODO list):
Firstly, we need to set up the newly provisioned git
user with suitable authorized_keys
(ref issue). For the purpose of this example, I'm going to copy my currently authorized_keys
across for the git
user:
cp ~/.ssh/authorized_keys /home/git/.ssh/authorized_keys chown git:git /home/git/.ssh/authorized_keys
At this point, it's worth opening new terminal window in your local environment also so we can clone a test application repository, and then deploy it to our server.
So in your local environment, clone the nodeapp-helloworld
repository:
git clone https://bitbucket.org/DamonOehlman/nodeapp-helloworld.git
cd nodeapp-helloworld
This will clone our simple node helloworld application locally. Feel free to npm install
locally and take it for a test drive if you wish. The next thing that we will be doing in the tutorial though is adding our remote deployment endpoint for the repo:
git remote add deploy git@yourhost.tld:apps/helloworld.git
The above line tells our local git repository that we have a remote called deploy
that we can now deploy to. Before copying and pasting the above line you will want to replace yourhost.tld
with either your target hostname or IP.
Once you have added the remote you should be able to push to the deploy endpoint:
git push deploy master
If everything goes to plan, your application will be pushed to the deploy
remote on the master
branch. Additionally, the git post-receive
hook will kick in and run the required deployment logic (running npm install
, restarting the upstart service, etc). The output that is generated should be similar to the following:
Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (4/4), done. Writing objects: 100% (5/5), 729 bytes, done. Total 5 (delta 0), reused 0 (delta 0) remote: ~/apps/helloworld/master ~/apps/helloworld.git remote: Already on 'master' remote: npm http GET https://registry.npmjs.org/express remote: npm http 304 https://registry.npmjs.org/express remote: npm http GET https://registry.npmjs.org/connect/2.9.0 remote: npm http GET https://registry.npmjs.org/commander/1.2.0 remote: npm http GET https://registry.npmjs.org/range-parser/0.0.4 remote: npm http GET https://registry.npmjs.org/mkdirp/0.3.5 remote: npm http GET https://registry.npmjs.org/cookie/0.1.0 remote: npm http GET https://registry.npmjs.org/buffer-crc32/0.2.1 remote: npm http GET https://registry.npmjs.org/methods/0.0.1 remote: npm http GET https://registry.npmjs.org/fresh/0.2.0 remote: npm http GET https://registry.npmjs.org/send/0.1.4 remote: npm http GET https://registry.npmjs.org/cookie-signature/1.0.1 remote: npm http GET https://registry.npmjs.org/debug remote: npm http 304 https://registry.npmjs.org/range-parser/0.0.4 remote: npm http 304 https://registry.npmjs.org/commander/1.2.0 remote: npm http 304 https://registry.npmjs.org/buffer-crc32/0.2.1 remote: npm http 304 https://registry.npmjs.org/cookie/0.1.0 remote: npm http 304 https://registry.npmjs.org/connect/2.9.0 remote: npm http 304 https://registry.npmjs.org/methods/0.0.1 remote: npm http 304 https://registry.npmjs.org/fresh/0.2.0 remote: npm http 304 https://registry.npmjs.org/send/0.1.4 remote: npm http 304 https://registry.npmjs.org/cookie-signature/1.0.1 remote: npm http 304 https://registry.npmjs.org/debug remote: npm http 304 https://registry.npmjs.org/mkdirp/0.3.5 remote: npm http GET https://registry.npmjs.org/mime remote: npm http GET https://registry.npmjs.org/keypress remote: npm http GET https://registry.npmjs.org/qs/0.6.5 remote: npm http GET https://registry.npmjs.org/bytes/0.2.0 remote: npm http GET https://registry.npmjs.org/pause/0.0.1 remote: npm http GET https://registry.npmjs.org/uid2/0.0.2 remote: npm http GET https://registry.npmjs.org/multiparty/2.1.8 remote: npm http 304 https://registry.npmjs.org/mime remote: npm http 304 https://registry.npmjs.org/keypress remote: npm http 304 https://registry.npmjs.org/uid2/0.0.2 remote: npm http 304 https://registry.npmjs.org/multiparty/2.1.8 remote: npm http 304 https://registry.npmjs.org/pause/0.0.1 remote: npm http 304 https://registry.npmjs.org/bytes/0.2.0 remote: npm http 304 https://registry.npmjs.org/qs/0.6.5 remote: npm http GET https://registry.npmjs.org/stream-counter remote: npm http GET https://registry.npmjs.org/readable-stream remote: npm http 304 https://registry.npmjs.org/stream-counter remote: npm http 304 https://registry.npmjs.org/readable-stream remote: express@3.4.0 node_modules/express remote: ├── methods@0.0.1 remote: ├── range-parser@0.0.4 remote: ├── cookie-signature@1.0.1 remote: ├── fresh@0.2.0 remote: ├── buffer-crc32@0.2.1 remote: ├── cookie@0.1.0 remote: ├── debug@0.7.2 remote: ├── mkdirp@0.3.5 remote: ├── commander@1.2.0 (keypress@0.1.0) remote: ├── send@0.1.4 (mime@1.2.11) remote: └── connect@2.9.0 (uid2@0.0.2, pause@0.0.1, qs@0.6.5, bytes@0.2.0, multiparty@2.1.8) remote: restart: Unknown instance: remote: helloworld start/running, process 1231 remote: ~/apps/helloworld.git To git@distractable.me:apps/helloworld.git * [new branch] master -> master
Now, if everything has gone to plan you should be able to browse to your application on:
http://yourhost.tld:3060/
Updated