How can I detect Heroku's environment?
Asked Answered
M

8

43

I have a Django webapp, and I'd like to check if it's running on the Heroku stack (for conditional enabling of debugging, etc.) Is there any simple way to do this? An environment variable, perhaps?

I know I can probably also do it the other way around - that is, have it detect if it's running on a developer machine, but that just doesn't "sound right".

Meacham answered 21/2, 2012 at 18:58 Comment(0)
J
27

An ENV var seems to the most obvious way of doing this. Either look for an ENV var that you know exists, or set your own:

on_heroku = False
if 'YOUR_ENV_VAR' in os.environ:
  on_heroku = True

more at: http://devcenter.heroku.com/articles/config-vars

Jakejakes answered 22/2, 2012 at 9:57 Comment(4)
Thanks, I hadn't noticed that you could set environment variables that way yet. This seems like the right way to do it.Meacham
shortcut: on_heroku = 'DYNO' in os.environBohunk
Do NOT use on_heroku = 'DYNO' in os.environ as suggested by tinchou. That environment variable is not set during certain buildpack actions, such as when collectstatic automatically runs for a django build. This is dang near impossible to debug – you're much better off using the above solution.Third
Isn't os.environ.get('YOUR_ENV_VAR') the suggested way of check for the existence of an environment variable?Elba
S
19

Similar to what Neil suggested, I would do the following:

debug = True
if 'SOME_ENV_VAR' in os.environ:
    debug = False

I've seen some people use if 'PORT' in os.environ: But the unfortunate thing is that the PORT variable is present when you run foreman start locally, so there is no way to distinguish between local testing with foreman and deployment on Heroku.

I'd also recommend using one of the env vars that:

  1. Heroku has out of the box (rather than setting and checking for your own)
  2. is unlikely to be found in your local environment

At the date of posting, Heroku has the following environ variables:

['PATH', 'PS1', 'COLUMNS', 'TERM', 'PORT', 'LINES', 'LANG', 'SHLVL', 'LIBRARY_PATH', 'PWD', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'DYNO', 'PYTHONHASHSEED', 'PYTHONUNBUFFERED', 'PYTHONHOME', 'HOME', '_']

I generally go with if 'DYNO' in os.environ:, because it seems to be the most Heroku specific (who else would use the term dyno, right?).

And I also prefer to format it like an if-else statement because it's more explicit:

if 'DYNO' in os.environ:
    debug = False
else:
    debug = True
Seamstress answered 4/10, 2013 at 14:38 Comment(2)
for security you should probably be DEBUG=False by default if you're doing this surely. Something like DEBUG=False; if not 'DYNO' in os.environ: debug=True perhaps?Martlet
In 2022 there is also the env var HEROKU_APP_DIR available - at least for PHP apps using Apache. I like it more as it clearly states HEROKU as its source.Sexpartite
P
16

First set the environment variable ON_HEROKU on heroku:

$ heroku config:set ON_HEROKU=1

Then in settings.py

import os

# define if on heroku environment
ON_HEROKU = 'ON_HEROKU' in os.environ
Phantom answered 26/11, 2013 at 20:19 Comment(2)
I prefer the DYNO solution (or set this on the web UI, not with config:set) since this will be True on heroku local too, meaning we can't use it to test if running on localhost or not.Alaric
@OllieFord I don't get DYNO on heroku local, so I had to explictly add DYNO=Dummy in my .env (Any value is fine since we are checking just the existence of the env. variable)Gould
Q
3

Read more about it here: https://devcenter.heroku.com/articles/config-vars

My solution:

$ heroku config:set HEROKU=1

These environment variables are persistent – they will remain in place across deploys and app restarts – so unless you need to change values, you only need to set them once.

Then you can test its presence in your app.:

>>> 'HEROKU' in os.environ
True
Quickel answered 1/11, 2013 at 19:48 Comment(0)
P
2

The most reliable way would be to set an environment variable as above. If that's not possible, there are a few signs you can look for in the filesystem, but they may not be / are not foolproof

  • Heroku instances all have the path /app - the files and scripts that are running will be under this too, so you can check for the presence of the directory and/or that the scripts are being run from under it.

  • There is an empty directory /etc/heroku

  • /etc/hosts may have some heroku related domains added ~ $ cat /etc/hosts <snip>.dyno.rt.heroku.com

Any of these can and may change at any moment.

Your milage may vary

Pyridine answered 18/1, 2017 at 5:20 Comment(0)
S
1

DATABASE_URL environment variable

in_heroku = False
if 'DATABASE_URL' in os.environ:
    in_heroku = True

I think you need to enable the database for your app with:

heroku addons:create heroku-postgresql:hobby-dev

but it is free and likely what you are going to do anyways.

Heroku makes this environment variable available when running its apps, in particular for usage as:

import dj_database_url
if in_heroku:
    DATABASES = {'default': dj_database_url.config()}
else:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        }
    }

Not foolproof as that variable might be defined locally, but convenient for simple cases.

heroku run env

might also show other possible variables like:

  • DYNO_RAM
  • WEB_CONCURRENCY

but I'm not sure if those are documented like DATABASE_URL.

Secretarial answered 13/5, 2016 at 13:44 Comment(2)
@Downvoters please explain so I can learn and improve info ;-)Secretarial
The environment variable DATABASE_URL is becoming more commonplace and not used only by Heroku. It's less and less likely to be accurate as time goes on.Pyridine
S
0

Short version: check that the time zone is UTC/GMT:

if not 'ORIGINAL_TIMEZONE' in os.environ:
    f = os.popen('date +%Z')
    tz = f.read().upper()
    os.environ['ORIGINAL_TIMEZONE']=tz


tz = os.environ['ORIGINAL_TIMEZONE']
if tz != '' and (not 'utc' in tz.lower()) and (not 'gmt' in tz.lower()):
    print 'Definitely not running on Heroku (or in production in general)'
else:
    print 'Assume that we are running on Heroku (or in production in general)'

This is more conservative than if tz=='UTC\n': if in doubt, assume that we are in production. Note that we are saving the timezone to an environment variable because settings.py may be executed more than once. In fact, the development server executes it twice, and the second time the system timezone is already 'UTC' (or whatever is in settings.TIMEZONE).

Long version:

making absolutely sure that we never run on Heroku with DEBUG=True, and that we never run the development server on Heroku even with DEBUG=False. From settings.py:

RUNNING_DEV_SERVER = (len(sys.argv) > 1) and (sys.argv[1] == 'runserver')

DEBUG = RUNNING_DEV_SERVER

TEMPLATE_DEBUG = DEBUG

# Detect the timezone
if not 'ORIGINAL_TIMEZONE' in os.environ:
    f = os.popen('date +%Z')
    tz = f.read().upper()
    os.environ['ORIGINAL_TIMEZONE']=tz
    print ('DEBUG: %d, RUNNING_DEV_SERVER: %d, system timezone: %s ' % (DEBUG, RUNNING_DEV_SERVER, tz))


if not (DEBUG or RUNNING_DEV_SERVER):
    SECRET_KEY = os.environ['SECRET_KEY']
else:
    print 'Running in DEBUG MODE! Hope this is not in production!'

    SECRET_KEY = 'DEBUG_INSECURE_SECRET_KEY_ae$kh(7b%$+a fcw_bdnzl#)$t88x7h2-p%eg_ei5m=w&2p-)1+'

    # But what if we are idiots and are still somehow running with DEBUG=True in production?!
    # 1. Make sure SECRET_KEY is not set
    assert not SECRET_KEY in os.environ
    # 2. Make sure the timezone is not UTC or GMT (indicating production)

    tz = os.environ['ORIGINAL_TIMEZONE']
    assert tz != '' and (not 'UTC' in tz) and (not 'GMT' in tz)

    # 3. Look for environment variables suggesting we are in PROD
    for key in os.environ:
        for red_flag in ['heroku', 'amazon', 'aws', 'prod', 'gondor']:
            assert not red_flag in key.lower()
            assert not red_flag in os.environ[key].lower()

If you really want to run the development server on Heroku, I suggest you add an environment variable specifying the date when you can do that. Then only proceed if this date is today. This way you'll have to change this variable before you begin development work, but if you forget to unset it, next day you will still be protected against accidentally running it in production. Of course, if you want to be super-conservative, you can also specify, say, a 1-hour window when exceptions apply.

Lastly, if you decided to adopt the approach suggested above, while you are at it, also install django-security, add djangosecurity to INSTALLED_APPS, and add to the end of your settings.py:

if not (DEBUG or RUNNING_DEV_SERVER):
    ### Security
    SECURE_SSL_REDIRECT = True
    SECURE_CONTENT_TYPE_NOSNIFF = True

    SECURE_HSTS_SECONDS = 86400000
    SECURE_HSTS_INCLUDE_SUBDOMAINS = True
    SECURE_BROWSER_XSS_FILTER = True

    SESSION_COOKIE_SECURE = True
    SESSION_COOKIE_HTTPONLY = True
    CSRF_COOKIE_HTTPONLY = True # May have problems with Ajax
    CSRF_COOKIE_SECURE = True
String answered 22/4, 2014 at 17:59 Comment(0)
C
0

There is no need to set your own config variable.

The best one to use here is $DYNO. It is documented, and always set by Heroku, both during build time and runtime.

(Heroku Labs Dyno Metadata provides some other options, but they only are set when Dyno Metadata is turned on. They haven't changed in years nor been added to the platform, but theoretically they are more "subject to change.")

Cotenant answered 18/12, 2023 at 18:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.