{"id":115,"date":"2020-12-04T11:07:31","date_gmt":"2020-12-04T11:07:31","guid":{"rendered":"http:\/\/blogs.igalia.com\/adunaev\/?p=115"},"modified":"2021-02-17T10:45:04","modified_gmt":"2021-02-17T10:45:04","slug":"django-postgres-nginx-uwsgi","status":"publish","type":"post","link":"https:\/\/blogs.igalia.com\/adunaev\/2020\/12\/04\/django-postgres-nginx-uwsgi\/","title":{"rendered":"Django + Postgres + nginx + uWSGI = \u2764\ufe0f"},"content":{"rendered":"<p>To date, I set up a production web service thrice. (Yeah, I am a full stack dev!)<\/p>\n<p>The first time I did many mistakes. That website still works, I maintain and update it, but now it would be difficult to fix things, I would have to stop the service and have some quite stressful time re-configuring it.<\/p>\n<p>The second time I did it more or less right, but the deployment happened in the corporate network of the company that I no longer work for, so that knowledge is now lost to me.<\/p>\n<p>When I tried to do it the third time, I had to recall and google many things again.  Now it works, so it is a good moment to write a note to myself in future.<\/p>\n<h3>The plan<\/h3>\n<p><em>Five steps to quick and easy deployment<\/em> by Alex who likes to code:<\/p>\n<ol>\n<li>Install nginx, uwsgi, python3, and postgres.  (no-op)<\/li>\n<li>Set up PostgreSQL.<\/li>\n<li>Check out the Django project and set it up.<\/li>\n<li>Configure uWSGI.<\/li>\n<li>Configure nginx.<\/li>\n<\/ol>\n<p>Setting up prerequisites is literally one call to the package manager (yes, I assume that we are on Linux).  Too easy, I cannot write anything else about that.  Other parts are longer.<\/p>\n<h3>PostgreSQL<\/h3>\n<p>As user <code>postgres<\/code>, run <code>psql<\/code>. This will take us into the DBMS console. There, run the following:<\/p>\n<pre><code>CREATE DATABASE db-name;\nCREATE USER db-user WITH PASSWORD 'password';\nALTER ROLE db-user SET client_encoding TO 'utf8';\nALTER ROLE db-user SET default_transaction_isolation\n    TO 'read committed';\nALTER ROLE db-user SET timezone TO 'UTC';\nGRANT ALL PRIVILEGES ON DATABASE db-name TO db-user;\n<\/code><\/pre>\n<p>Most commands above  are quite self-explanatory. The <code>ALTER ROLE<\/code> ones are not mandatory but may be useful.<\/p>\n<p>Now exit the DBMS console and proceed to the next part.<\/p>\n<h3>Django<\/h3>\n<p>This part is the longest, it is in fact the actual deployment of the project, other parts are just\u2026 <em>environment<\/em>.<\/p>\n<ol>\n<li>Switch to the user that will normally do whatever is required regarding the source code, e.g., updating the checkout, editing the code and project configs, etc.  Let it be user <em>project<\/em>.<\/li>\n<li>Check out the project into the common location for websites, like <code>\/var\/www<\/code> on Debian.  Let it be <code>\/var\/www\/project<\/code> (we will need it later a few more times).<\/li>\n<li>Create the virtual Python environment (<em>venv<\/em>); modern versions of Python can do that themselves via <code>python -m venv {directory name}<\/code>, I like to have it in the <code>venv<\/code> directory directly within the Django project, so the full path would be <code>\/var\/www\/project\/venv<\/code>.<\/li>\n<li>Activate the venv and install Python requirements.  Do not deactivate it for the next few steps.<\/li>\n<li>Edit the project settings.  See remarks below for some ideas about this.<\/li>\n<li>Test that the project is configured properly by running <code>manage.py migrate<\/code>.<\/li>\n<li>Test even more that the project is configured properly by running <code>manage.py runserver<\/code>.  Terminate the server after it starts normally.<\/li>\n<li>Create the Django superuser by running <code>manage.py createsuperuser<\/code>.  We may deactivate the venv now.<\/li>\n<\/ol>\n<h4>Remarks on preparing the project for deployment<\/h4>\n<p>The web service always has some site-specific settings, and putting them under the version control would be a pain.  I like the idea of moving these settings into the separate file from the very beginning.  Create <code>site_settings.py<\/code> near the standard <code>settings.py<\/code>, cut and paste whatever is going to differ between hosts, and then add <code>from .site_settings import *<\/code> into <code>settings.py<\/code>.  Add <code>site_settings.py<\/code> into <code>.gitignore<\/code>.<\/p>\n<p>We definitely want <code>SECRET_KEY<\/code>, <code>DEBUG<\/code>, and <code>DATABASES<\/code> to stay in the site config.<\/p>\n<h3>uWSGI<\/h3>\n<p>As root, create <code>\/etc\/uwsgi\/apps-available\/project.ini<\/code> (the <code>.ini<\/code> extension is important!) and put this inside:<\/p>\n<pre><code>[uwsgi]\n# The root directory of the Django project.\nchdir  = \/var\/www\/project\n# The Python module within the project that creates\n# the application.  It is already there, built into\n# every Django project.\nmodule = project.wsgi\n# The venv that should be used.\nhome   = \/var\/www\/project\/venv\n\n# Used to connect to the web server.\nsocket       = \/var\/www\/project\/socket\n# uWSGI has plugins for different platforms.\n# This may require to install something like\n# uwsgi-plugin-python3.\nplugins      = python37\n\n# Not important :-D but still needed.\nmaster       = true\nprocesses    = 5\nvacuum       = true\n<\/code><\/pre>\n<p>Create a link to the newly created config to enable the application:<\/p>\n<pre><code>ln -s \/etc\/uwsgi\/apps-available\/project.ini \\\n        \/etc\/uwsgi\/apps-enabled\/project.ini\n<\/code><\/pre>\n<p>Restart uwsgi and ensure that it runs with no errors.<\/p>\n<h3>nginx<\/h3>\n<p>As user <em>project<\/em>, create <code>\/var\/www\/project\/project\/uwsgi_params<\/code> and put there this:<\/p>\n<pre><code>uwsgi_param QUERY_STRING $query_string;\nuwsgi_param REQUEST_METHOD $request_method;\nuwsgi_param CONTENT_TYPE $content_type;\nuwsgi_param CONTENT_LENGTH $content_length;\nuwsgi_param REQUEST_URI $request_uri;\nuwsgi_param PATH_INFO $document_uri;\nuwsgi_param DOCUMENT_ROOT $document_root;\nuwsgi_param SERVER_PROTOCOL $server_protocol;\nuwsgi_param REMOTE_ADDR $remote_addr;\nuwsgi_param REMOTE_PORT $remote_port;\nuwsgi_param SERVER_ADDR $server_addr;\nuwsgi_param SERVER_PORT $server_port;\nuwsgi_param SERVER_NAME $server_name;\n<\/code><\/pre>\n<p>(This is in fact the standard file that is shipped with the nginx distribution.)<\/p>\n<p>Now switch to root, create <code>\/etc\/nginx\/sites-available\/project<\/code> and put this inside:<\/p>\n<pre><code>server {\n    listen      80;\n    server_name project.org;\n    charset     utf-8;\n\n    location \/static\/ {\n        alias \/var\/www\/project\/static\/;\n    }\n\n    location \/ {\n        \\# Connection to uWSGI.\n        uwsgi_pass unix:\/var\/www\/project\/socket;\n        include    \/var\/www\/project\/project\/uwsgi_params;\n    }\n}\n<\/code><\/pre>\n<p>Create a link to the newly created config to enable the website:<\/p>\n<pre><code> ln -s \/etc\/nginx\/sites-available\/project \\\n    \/etc\/nginx\/sites-enabled\/project\n<\/code><\/pre>\n<p>Restart nginx and ensure that it runs without errors.<\/p>\n<p>That is it!<\/p>\n<h3>References<\/h3>\n<ul>\n<li><a href=\"https:\/\/blog.doismellburning.co.uk\/django-an-unofficial-opinionated-faq\/\">An unofficial opinionated FAQ on Django<\/a><\/li>\n<li><a href=\"https:\/\/docs.djangoproject.com\/en\/dev\/\">Django documentation<\/a><\/li>\n<li><a href=\"https:\/\/www.postgresql.org\/docs\/\">PostreSQL documentation<\/a><\/li>\n<li><a href=\"https:\/\/uwsgi-docs.readthedocs.io\/en\/latest\/tutorials\/Django_and_nginx.html\">uWSGI documentation<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>To date, I set up a production web service thrice. (Yeah, I am a full stack dev!) The first time I did many mistakes. That website still works, I maintain and update it, but now it would be difficult to fix things, I would have to stop the service and have some quite stressful time [&hellip;]<\/p>\n","protected":false},"author":60,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-115","post","type-post","status-publish","format-standard","hentry","category-django"],"_links":{"self":[{"href":"https:\/\/blogs.igalia.com\/adunaev\/wp-json\/wp\/v2\/posts\/115","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.igalia.com\/adunaev\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.igalia.com\/adunaev\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.igalia.com\/adunaev\/wp-json\/wp\/v2\/users\/60"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.igalia.com\/adunaev\/wp-json\/wp\/v2\/comments?post=115"}],"version-history":[{"count":46,"href":"https:\/\/blogs.igalia.com\/adunaev\/wp-json\/wp\/v2\/posts\/115\/revisions"}],"predecessor-version":[{"id":164,"href":"https:\/\/blogs.igalia.com\/adunaev\/wp-json\/wp\/v2\/posts\/115\/revisions\/164"}],"wp:attachment":[{"href":"https:\/\/blogs.igalia.com\/adunaev\/wp-json\/wp\/v2\/media?parent=115"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.igalia.com\/adunaev\/wp-json\/wp\/v2\/categories?post=115"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.igalia.com\/adunaev\/wp-json\/wp\/v2\/tags?post=115"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}