Here's another interesting story about how we do things at Browserling. This time the story is about how we use ploy to deploy code at Browserling and Testling.

First off, what is ploy? Ploy is a deployment system that we created at Browserling, that includes a http router, a git endpoint and a process manager all in one. You just push your code at ploy, and it deploys it and runs the necessary processes.

Ploy overview

Here's a simple overview and an example of how to get started with ploy. First, setup auth.json that ploy will use for git authentication when you git push at it:

$ cat auth.json
  "pkrumins" : "password",
  "substack" : "password2"

Then setup a git remote in your app's repository:

$ git remote add ploy

Ploy uses the special /_ploy/NAME.git url as a git endpoint.

Then start ploy:

$ ploy ./myapp -a auth.json --port 80

This will start ploy on port 80 and use ./myapp directory to store the repository and logs. Surely you never want to run any code as a privileged user, so see the section below on how to setup ploy on port 8080 and redirect port 80 with iptables to port 8080.

Your app should have a package.json file, such as the following:

  "name": "myapp",
  "private": true,
  "version": "0.0.1",
  "scripts": {
    "start": {
      "index": "node server.js",
      "stats": "node stats/server.js",
      "_auth": "node auth/server.js"
    "postinstall": "./"

The scripts.start lists processes that ploy will manage. The index process will handle requests to and, whereas stats will handle requests to the subdomain. The process starting with an underscore _auth is a non-http process that will simply be run by ploy and won't be mapped to any http requests.

Now git push your code to ploy:

$ git push ploy master

When you push code at a ploy server, your processes will be started and any previous processes running under the same branch name will be killed. Ploy does not detach your server processes. When the ploy server goes down, it takes the processes it started with it. However, when the ploy server is started back up, it will attempt to restart all of the processes it was previously running.

Before starting the processes, ploy will run npm install . that will handle the scripts.postinstall (also install and preinstall) hooks. Ploy'll set the PORT environment variable that your application should listen to. It will then route all the http requests on port 80 to your application listening on PORT.

You can also setup staging branches easily with ploy, just push to ploy like this:

$ git push ploy master:staging

This will make the staging branch available at (given that you've an A DNS record for that points to the same server that does). The stats process will be available at

Once you're done with your staging branch, you can simply move staging to production (master branch) by running ploy mv staging master. That will re-route the connections and you'll instantly be running your staging in production.

Ploy provides many commands for managing processes, logs, and branches. You can list the processes by running ploy ls from your repository. You can restart them by running ploy restart <process name>. You can view the logs through ploy log, and manage branches branches through ploy mv, ploy rm commands, and ploy redeploy commands.

Ploy makes it really easy to deploy your apps to production and staging! Read more about it on ploy's project page!

Ploy at Browserling and Testling

Now let's turn to how we use ploy in production to deploy Browserling and Testling. We've a more complex ploy setup, where we use ssl certificates for running https, and a request router for more complicated routing.

Here's how we run ploy for Testling:

ploy ./testling \
  -a auth.json \
  --port 8000 \
  --sslPort 8443 \
  --ca=./certs/bundle.crt \
  --cert=./certs/ci_testling_com.crt \
  --key=./certs/server.key \
  -f testling-router.js
  • The -a argument specifies the authentication info for the git endpoint.
  • The --port argument specifies the http port that ploy will listen on.
  • The --sslPort argument specifies the https port that ploy will listen on.
  • The --ca, --cert, and --key argument setup the ssl cert.
  • The -f argument specifies the http router that ploy will use to route the connections.

We run ploy as non-root and it listens on port 8000 for http connections, and on port 8443 for https connections. We've the following iptables rules setup on the testling server that redirect ports 80 to 8000, and 443 to 8443:

iptables -A PREROUTING -t nat -p tcp --dport 80 -j REDIRECT --to-port 8000
iptables -t nat -I OUTPUT -p tcp -d --dport 80 -j REDIRECT --to-ports 8000
iptables -t nat -I OUTPUT -p tcp -d --dport 80 -j REDIRECT --to-ports 8000

iptables -A PREROUTING -t nat -p tcp --dport 443 -j REDIRECT --to-port 8443
iptables -t nat -I OUTPUT -p tcp -d --dport 443 -j REDIRECT --to-ports 8443
iptables -t nat -I OUTPUT -p tcp -d --dport 443 -j REDIRECT --to-ports 8443

Auth file auth.json contains the user access info, in our case it's just me and substack:

{ "pkrumins": "pass1", "substack": "pass2" }

The router file is used in more complicated http routing cases. If we didn't have the testling-router.js router, then all the requests to ports 80 and 443 would simply be routed to app's PORT (environment variable that was set by ploy before it started the processes), but in our case we redirect all unencrypted http connections to https:

module.exports = function (req, res, bounce) {
    if (!req.connection.encrypted) {
        if ( {
            var hostname =':')[0];
            if (hostname == '') {
                for (var i = 0; i < this.bouncers.length; i++) {
                    if (!this.bouncers[i].key) continue;
                    res.statusCode = 302;
                    res.setHeader('location', '' + req.url);
                    return res.end();
                res.statusCode = 404;
                return res.end('no https endpoint configured\n');

What this router does is it checks if a connection is encrypted, and if it's not, it simply sets the Location: header and the browser redirects the visitors to https.

You can do all kinds of crazy things with a router, for example, route based on the source/destination IP, or route based on http headers, route based on date/time, etc.

Here's how the root directory package.json looks for Testling:

  "name": "testling-ci",
  "private": true,
  "version": "0.0.1",
  "scripts": {
    "start": {
      "ci": "node ci/server.js",
      "git": "node git/server.js"
    "postinstall": "./"

And here's how the postinstall script looks like:


(cd ci; npm install .)
(cd git; npm install .)

What this script does is it simply goes into the ci/ and git/ directories and npm installs the modules. After it's done, ploy starts the and scripts.start.git processes that map to and subdomains.

Browserling uses pretty much the same arguments to ploy but has a much more complicated router that uses seaport:

var seaport = require('seaport');
var config = require('figc')(__dirname + '/config.json');
var pick = require('deck').pick;

var ports = seaport.connect(config.seaport);
ports.on('connect', function () { console.log('browserling-router.js: connected to seaport') });
ports.on('disconnect', function () { console.log('browserling-router.js: disconnected from seaport') });

module.exports = function (req, res, bounce) {
    if ( &&':')[0] == '') {
        var ps = ports.query('web.status')
        if (ps.length) {
            return bounce(pick(ps), {
                headers: { 'x-forwarded-for': req.connection.remoteAddress }
        res.statusCode = 404;
        return res.end('no matching services are running for');

    if ( &&':')[0] == '') {
        console.log(req.method + ' request from ' + req.connection.remoteAddress + ' to ' + req.url);
        if (req.connection.encrypted || req.url == '/account/verify.json') {
            var ps = ports.query('web.browserling');
            if (ps.length) {
                return bounce(pick(ps), {
                    headers: { 'x-forwarded-for': req.connection.remoteAddress }
            res.statusCode = 404;
            return res.end('no matching services are running for\n');
        else {
            res.statusCode = 302;
            res.setHeader('location', '' + req.url);
            return res.end();
    else if ( &&':')[0] == '') {
        res.statusCode = 302;
        res.setHeader('location', '' + req.url);
        return res.end();


At Browserling all our services are registered with seaport, so the routing no longer happens to the port in the PORT environment variable. Instead the the application's port is requested through seaport's ports.query. I'll write more about how we use seaport at Brwoserling some time later.

Ploy has been really wonderful to work with. If you haven't used it and you're looking for a deployment system for node (and not just node!), definitely try it out!

Until next time!


nathan Permalink
March 18, 2014, 13:43

rubbish idiot

March 19, 2014, 07:57

thanks for great post

April 07, 2014, 05:28

I might accept that and try in life

April 16, 2014, 18:45

Hey guys,

I'm trying to do this on the Amazon EC2, do you have any clue about the needed configuration?

When I try to do git push ploy


it can't resolve the hostname....

May 22, 2014, 17:26

Great Post !!!
Check GSEB Science Result 2014 Now (From 7AM)

May 22, 2014, 17:29

GBSE results


June 02, 2014, 20:37

Clipping Path Outsource provides clipping path service from only $0.35. Photoshop masking,
drop shadow, image retouching, raster 2 vector, image manipulation services are also available.
Quality guaranteed, Express service, Payment required after job done.
Photoshop Clipping Path, Masking, Image Manipulation service provider. Free Trial,
Quality guaranteed, Express Delivery, Cheap cost, 24 hrs support.

June 05, 2014, 17:48

Writing Content which is loved by readers is really tough task but you did that great job easily.Thank you very much

ap eamcet 2014 ....

June 10, 2014, 13:35

Ok I will try that

July 04, 2014, 11:13

That is so right

October 26, 2014, 01:24

Artikel yang sangat bermanfaat, terima kasih admin =))

November 12, 2014, 08:05

Do you use Whatsapp. Check out Whatsapp dp, dp for Whatsapp
Best Whatsapp dp.

ng1 Permalink
November 12, 2014, 16:27

Hi How are you doing? I really hope you continue writing such great posts in future too.Local SEO Metrics, Bishal Biswas. I believe you're doing really great since I'm seeing a lot of engagement over here in this blog. Since you're doing great, I'm feeling inspired too to have similliar blog. Are you having any blog networks? I'm really eager to know so. Let me know if you've one.

Best SEO Company in India, Best SEO Company in Chennai, Affordable SEO Company in India. Affordable SEO Service in India.

Kaur deep Permalink
December 04, 2014, 03:08

Excellent and knowledgeable links and post.thanks for sharing.
Now, The Time has come for the Happy New Year 2015. It wil bring a lot fo happinesin our life.
Happy New Year 2015
And I am sure, You wil anjoy this new year day with yoru fiends and wil send some images wishes,
New Year 2015

Happy New Year 2015 Wishes
Greetings to yoru close ones, Your friends, You lover, etc..
Happy New Year 2015 Images
Even I am waititng for that day, because it just changes our life.
Happy New Year 2015 Greetings

Happy New Year 2015 SMS
So, That's why I am commenting here to wish you a very happy new year.
Happy New Year 2015 Messages
I hope this New year 2015 wil bring success to your website. But before that Merry Christmas is coming.
Merry Christmas Quotes
It is also a big event for everyone.
Merry Christmas Pictures
Everyone knows that Christmas is a day when People seems to be very happy, they just leave all sadness behind.
Merry Christmas Wallpapers
And I am sure, you also enjoy this Merry Christmas day of 2014.
Christmas day Images

Christmas stocking
But if you are not looking out to enjoy this christmas, then you are missing out on a big thing, because it really brings happiness.
Merry Christmas Images

Merry Christmas 2014
So, what you are waiting for?
Merry Christmas 2015
Enjoy This Merry Christmas day. Cheers :).

Leave a new comment

(why do I need your e-mail?)

(Your twitter name, if you have one. (I'm @pkrumins, btw.))

Type the word "halflife3_397": (just to make sure you're a human)

Please preview the comment before submitting to make sure it's OK.