HOWTO: Deploy a Catalyst application using FastCGI and nginx

Note: this is not the canonical way, it just Works For Meâ„¢ – there are, no doubt, vast improvements that could be made. YMMV.

0. ASSumptions

I’m assuming that the server already has the Catalyst framework installed, and any other Perl modules your app requires (that don’t live in the app’s lib directory). For the purpose of example, the application will be called “FooApp”, and will be installed on the server at /home/sites/foo.example.org

I’m assuming you want to run the application server privately (bound only to the localhost address) on port 8100.

1. Upload what you need from your app

You need to make sure you’ve got all of the Catalyst framework

From your application, you only need 4 things:

  1. The ‘lib’ directory (where all of your code is!)
  2. The ‘root’ directory (where all of you static stuff is)
  3. The ‘script’ directory (where the FastCGI wrapper is)
  4. The application config file (okay, you don’t need this if you put the config in you main App.pm file)

Upload them all to the /home/sites/foo.example.org directory. If you’re using the Template Toolkit view, you also need to make sure that the directory for compiled templates exists and is writeable by the nginx user.

2.Set up nginx

There’s nothing particularly special here:

server {
  server_name  foo.example.org;
  # Let's have a server alias as well
  server_name  otherfoo.example.org;

  access_log  /var/log/nginx/foo.access.log;
  root   /home/sites/foo.example.org;

  # Serve static content statically
  expires +30d;
  location /css {
    add_header Cache-control public;
    root /home/sites/foo.example.org/root/;
  }
  location /js {
    add_header Cache-control public;
    root /home/sites/foo.example.org/root/;
  }
  location /images {
    add_header Cache-control public;
    root /home/sites/foo.example.org/root/;
  }

  # We pass the rest to our FastCGI application server
  location /  {
    # We also set some headers to prevent proxies
    add_header Pragma "no-cache";
    add_header Cache-control "no-cache, must-revalidate, private, no-store";
    expires -1s;

    # Where our FastCGI app server is listening
    fastcgi_pass   127.0.0.1:8100;

    include /etc/nginx/fastcgi_params;
    fastcgi_param   SCRIPT_NAME     /;
    fastcgi_param   PATH_INFO       $fastcgi_script_name;
  }
}

I’ve got all of the stuff that the Static::Simple server was handling served by nginx directly, with nice Expiry: and Cache-control: headers to allow proxy/browser caches to be used (no point serving them the same files again and again)

You should now be able to request the static content (CSS/JavaScript/images) from your site – if not, there’s something wrong with your nginx configuration. Fix it first.

If  you try to run any part of the dynamic (code) side, you should get a nice “502 Bad Gateway” message. That’s fine – it’s what you want, in fact – as you’re not running your app yet. 🙂

3. Run your application server

This involves running the FastCGI wrapper script from the ‘script’ directory you uploaded.

Important: if you use a config file, you need to move it inside of the lib/$appname directory for it to be read.

It’s probably a very good idea to run the FastCGI server manually, with “CATALYST_DEBUG=1”, to make sure the app works properly. Once you’ve confirmed it works, you can add it to the system startup.

Here’s the init.d file I use: (probably very Debian specific)

#! /bin/sh
### BEGIN INIT INFO
# Provides:          application-catalyst-foo
# Required-Start:    $local_fs $network
# Required-Stop:     $local_fs $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Starts the FastCGI app server for the "FooApp" site
# Description:       The FastCGI application server for the "FooApp" site
### END INIT INFO

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
SITE_HOME=/home/sites/foo.example.com
DAEMON=$SITE_HOME/script/fooapp_fastcgi.pl
OPTS="-l :8100 -n 5"
NAME=catalyst-app-foo
DESC="'Foo' app server"
USER=www-data

test -f $DAEMON || exit 0

set -e

start_daemon()
{
 echo -n "Starting $DESC: "
 start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid -d $SITE_HOME \
 --exec /usr/bin/perl --startas $DAEMON --chuid $USER --background --make-pid -- $OPTS
 echo "$NAME."
}

stop_daemon()
{
 echo -n "Stopping $DESC: "
 start-stop-daemon --stop --signal TERM --pidfile /var/run/$NAME.pid
 echo "$NAME."
}

reload_daemon()
{
 echo -n "Reloading $DESC: "
 start-stop-daemon --stop --signal HUP --pidfile /var/run/$NAME.pid
 echo "$NAME."
}

case "$1" in
 start)
 start_daemon
 ;;
 stop)
 stop_daemon
 ;;
 reload)
 reload_daemon
 ;;
 restart|force-reload)
 stop_daemon
 sleep 5
 start_daemon
 ;;
 *)
 N=/etc/init.d/$NAME
 echo "Usage: $N {start|stop|reload|restart|force-reload}" >&2
 exit 1
 ;;
esac

exit 0

In “OPTS”, we set the listening port for the app server – this must be the same as the nginx config, otherwise you’re still going to get 502 errors. I’ve set 5 handlers – depending on your app, you may want to increase that number.

Add the initscript to whichever runlevels you want, so it’ll be run on startup, and/or…

4. Set up your favourite process monitor to ensure that the server is always running

I usually use monit – it’s easy enough to set up, using the PID file and init script to check, start and stop the app server.

And that should be that – your Catalyst application should now be up and running through nginx.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.