Django simple deployment with gunicorn and whitenoise
May 03, 2020 Durai Pandian
If you are looking for simple production deployment without much configuration or not making much configuration from your development. The following whitenoise, gunicorn packages will help you deploy it within 10 minutes. This setup is not applicable for high traffic websites which needs load balancing and HTTPS. For that situation, You need to deploy with nginx. 1000 users per minute is an ideal candidate website.
But It is very important to change few configurations, before deploying in external server or cloud services. You have to prepare your application for production ready.
Why not default configuration
You can not use default configuration with DEBUG=True and runserver for production deployment, as it is only meant for development purpose. You have to change a bit for production deployment. This will give security-vise values to your application.
- Running server with DEBUG=True will display all the important trace in the response. This will expose lot of details about your application code in case if the exception is not handled in the application.
- Django "runserver" is not tested for production. I have seen socket errors in the runsever while in development. So I would suggest not to use this for production.
Production mode, DEBUG=False
You have to set up a web server to serve static files. But for small scale Django application, you can use whitenoise, gunicorn. For my current blog, I am using the same configuration and SQLite database. It is working well for readers.
One important thing is that you have to remove your SECRET_KEY value from the settings.py file and set as environment variable.
SECRET_KEY = 'lkeq)cq&pIPACEDSOMEDUMMY80hnyVALsvwfwUEyvherecmlqkinDONTcr%KEEP9kmIN@cS$CeT'
SECRET_KEY should not be placed in the settings.py file for number of reasons. CSRF tokens in Django are generated based on the SECRET_KEY.
- Accidentally DEBUG=True is set, your key will be exposed in case of error.
- If you share your project to some developer or git repo, it will be become security risk.
SECRET_KEY = os.environ['SECRET_KEY']
If you have already shared with someelse and want to change it now. You can follow below steps to generate new SECRET_KEY from Django management utils.
from django.core.management.utils import get_random_secret_key
You have to set ALLOWED_HOSTS in the settings.py.
ALLOWED_HOSTS = ['127.0.0.1']
Gunicorn is a python HTTP WSGI server. This is an application server which executes your view and send response back. Gunicorn is based on pre-fork or master-slave model where master controls N no. of workers which handles the incoming requests.
Install gunicorn with below pip command.
Gunicorn listens to 8000 port and creates one worker and one master by default. But you can bind with -b :80 or --bind :80 to listen 80 port and assign workers with -w 2 or --workers=2. As workers are different processes, it should be carefully assigned. You can explore configuration in officials docs. But below command will get the job done for basic server.
(blog) ➜ thuruthuru git:(development) ✗ pip install gunicorn
(blog) ➜ thuruthuru git:(development) ✗ gunicorn thuruthuru.wsgi -b :80 -w 2
[2020-05-04 02:52:39 +0530]  [INFO] Starting gunicorn 20.0.4
[2020-05-04 02:52:39 +0530]  [INFO] Listening at: http://127.0.0.1:80 (41797)
[2020-05-04 02:52:39 +0530]  [INFO] Using worker: sync
[2020-05-04 02:52:39 +0530]  [INFO] Booting worker with pid: 41800
Application access after gunicorn setup
After running your Django application with Gunicorn, try accessing your website http://127.0.0.1/. Your website is disoriented, no static files and images are loaded. That is because gunicorn will not serve them. It is application server not a web server like Nginx.
Gunicorn is designed to act as application server which executes your application code to perform requested task and give results back to web server. Then web server serves to client.
As we are focusing on minimal production setup, we will use whitenoise to serve static files from application server (gunicorn) itself.
Whitenoise is simple package which facilitates serving static files from application server. It has compression storage enabled out of the box to serve the request. Install whitenoise using below pip command.
(blog) ➜ thuruthuru git:(development) ✗ pip install whitenoise
Adding whitenoise middlewareAdd whitenoise middleware to Django middleware in the settings.py file.
MIDDLEWARE += ['whitenoise.middleware.WhiteNoiseMiddleware']
Adding whitenoise storage
If you wish to add compression to your static files, add below line. You can skip if you don't want it.
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
Static root and URL
Make sure that you have static URL and static root is set in the settings.py files.
- STATIC_ROOT - This is the place where static files will be collected by Django collectstatic process.
- STATIC_URL - This is the URL path which will be referred in the application. Example: http://127.0.0.1/static/logo.png logo.png can be served from your development or production. But URL remains same as static.
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_URL = '/static/'
As the name implies, It collects static files from all the INSTALLED_APPS to STATIC_ROOT directory which we configured above to serve.
(blog) ➜ thuruthuru git:(development) ✗ python manage.py collectstatic
Application access after whitenoise setup
Now, the application will serve static files in the production mode. You see the weird alphanumeric value happened into the static files. That is MD5 hash value for versioning the static files. This is the result of adding whitenoise storage which compresses and does versioning.
If you modify app.css and run collectstatic, next time you will see different hash for app.css. It will help to load the latest file if the file is cached at client side.
Changing admin URL
This is just for fun. If you have admin URL for your application and not comfortable to exposing admin URL like this http://127.0.0.1:8000/admin/, you can generate it with random string.
from django.core.management import utilsUpdate the result string in your urls.py. If someone tries to access admin URL, it will not work.
Thanks for reading.!
Please comment if you have any questions or stuck somewhere with this post. Happy coding.!