Recently I’ve made a few small python web projects that I’ve deployed on Heroku. For some of these projects I’ve needed to run asynchronous jobs through Celery.
Heroku allows you to run one free dyno (or actually they give you 720 free dyno hours per month, which corresponds to one dyno constantly running). This means that if you choose to run one web dyno and one worker dyno (celery in this case), you’ll be charged for 720 dyno hours. However, if you have a very small project, or your’re working on a project that hasn’t been released yet, you can avoid this cost.
A heroku dyno is like a process, and in this process you can actually spawn new processes, as long as you stay within the limit of 512 mb ram (the process also only has one CPU core). Heroku suggests that you use foreman when you run your application on your local machine, but you can actually use foreman on heroku, in order to run multiple processes in a single dyno.
1. The first thing we’ll do is to create a post_compile script in the bin directory of our project. In this script we’ll add a few lines that will install foreman and make it available in our project slug. Create the file PROJECT_ROOT/bin/post_compile with the following content:
#!/usr/bin/env bash # set terminal to UTF8 (otherwise `gem install foreman` fails) export LANG=en_CA.UTF-8 # set up ruby env mkdir .ruby-gems export GEM_HOME=$PWD/.ruby-gems export PATH=$PATH:$PWD/.ruby-gems/bin # install foreman gem install foreman
2. Next, we’ll create a new procfile, in the project root directory, which we’ll call ProcfileFree:
web: gunicorn -b 0.0.0.0:$PORT -w 4 myapp.wsgi:application celery: python manage.py celeryd -c 3 --beat
3. The third thing to do is to update the “real” Procfile that Heroku uses, so that it starts foreman with our new ProcfileFree. The content of my Procfile is:
web: env > .env; env GEM_HOME=$HOME/.ruby-gems env PATH=$PATH:$HOME/.ruby-gems/bin foreman start -f ProcfileFree
And that’s all! After deploying the above, and scaling the web dyno to 1, Heroku will start foreman which will start the processes that you’ve defined in ProcfileFree (in my example this was gunicorn and celery through Django’s manage.py).
This method can be very useful for small projects sometimes, but it will not be performant. If you get more traffic than it can handle, scaling dynos the normal way is definitely the way to go.
Also note that when you’re only running a single dyno, Heroku will stop it if it hasn’t received any web requests in a while. One possible way to get around this is to ping your website with http requests regularly.
Lastly, I just want to say that I think Heroku is a great service, and I don’t mind paying for it at all, which I do for a few larger projects that I host there.