- PostgreSQL
- The TinyMCE editor
- HTML templates
- CSS
- Directory structure from docs.djangoproject.com.
- A PostgreSQL database
- gunicorn to serve the website
- nginx to 'reverse proxy' requests from outside to gunicorn
- PyCharm for local and remote development
- supervisor to manage gunicorn
This guide has been tested a lot of times on Ubuntu 16.04, 18.04, Raspbian Buster and even Arch Linux.
- Instructions to run the website locally
- Deploying on an Ubuntu server
- Create a project either by creating a new Django project in PyCharm and then copying the files from this project or by downloading this template project directly.
- Probably you want to use a virtual environment.
- Check that the packages in requirements.txt are installed, on Windows you may need to download the
mysqlclient
package from lfd.uci.edu selecting the right bits version for you python (you can check that by startingpython
), copy it to the project location, check there withpip -V
that you are using the pip of the virtual environment and then runpip install mysqlclient-1.3.13-cp37-cp37m-win32.whl
. On Linux you can download amysqlclient
package from your distro's package repo, you also needgcc
. - If you use PyCharm, you can make a Django Server run configuration (instead of running
runserver
all the time). Add an environment variable with nameDJANGO_SETTINGS_MODULE
and valuemysite.settings.development
, assuming the settings are indevelopment.py
in a foldersettings
in the foldermysite
. The development settings are for development on your local computer, production settings are for production on the server. - Tip: If you try running with
DEBUG=False
on your local computer, Django won't serve your static files for you since this is only meant for in production. - Possibly you need to select your Python interpreter.
-
To use a database locally, do the following. On Linux you can also not use pgAdmin but do everything via the command line, just continue to set up postgres like below and then go the instructions linked there.
- Install PostgreSQL (go to the website on Windows, use your package manager on Linux)
- Install pgAdmin (these instructions were tested on Windows with pgAdmin 4 2.0 and on Arch Linux with pgAdmin 4 4.1) or use the command prompt PostgreSQL tools for the next steps
- On Windows:
- To create a user, right-click on PostgreSQL and choose create Login/Group Role, give it for example the name of your project.
- Create a database by right-clicking on Databases, give it a name for example myproject_db, and under the 'security' tab grant all privileges to the user you just created.
- On Linux (tested on Arch Linux, on other distros commands may differ)
- Switch to the postgres user with
sudo su postgres
- Initialize postgres with
initdb --locale en_US.UTF-8 -D '/var/lib/postgres/data'
- Switch back to your own user (or open a new terminal window)
- Start the postgresql service with
sudo systemctl start postgresql
andsudo systemctl enable postgresql
- Change to a directory which
psql
can access, likecd /tmp
- Either use pgAdmin, or reuse the way of working from the server: do the first part of the postgres section below (until you exit psql) now. If you use pgAdmin:
- Create a server with a name like
mysite_server
and host name127.0.0.1
and user postgres, empty password. - Right click on Login/Group Roles and Create a Role, name it something like
mysite_user
and under Privileges give it the login privilege. - If you get the error
'psycopg2.extensions.Column' object has no attribute '_asdict'
then you have a version mismatch between psycopg2 and pgAdmin, probably psycopg2 is newer than your pgAdmin version (e.g. pgAdmin4 4.4 and psycopg2 2.8.2 will not work). Try updating everything (for newer pgAdmin version you can search online) and if it doesn't help use the command line (link above) to continue. - Create a database by right-clicking on Databases, give it a name for example myproject_db, select as owner mysite_user and under the 'security' tab add a privilege with grantee mysite_user, grant all privileges to the user you just created.
- Switch to the postgres user with
- Replace name, user and password in
DATABASES
in your settings file.
-
Use edit | find | replace in path to replace all references to 'mysite' to our own project name. Also rename the 'mysite' module (take care to be consistent with capitalization).
-
(Professional edition of PyCharm only) To run
manage.py
tasks, go to settings | Languages and Frameworks | Django and specify your settings file (in this case development, which includes base). Then you can use Tools | Run manage.py Task (CTRL
+ALT
+R
) to run tasks likemigrate
. -
Each time after you made changes in your models in models.py, run
makemigrations
andmigrate
to apply the changes to the database. Do that now. -
Also as
manage.py
task, runcreatesuperuser --username myname
to create a superuser (for example you) for your website backend. -
Use the run configuration you made or run
runserver
. -
Access the backend by navigating to http://127.0.0.1:8000/admin
(tip) If you already have a local website running, changing the port number allows you to keep things separate.
There are a lot of tutorials around, but I have noticed that instructions get obsolete very quickly, so preferably select the latest one you can find which uses exactly all the tools you want. For me that was the one I had to write myself, as below.
We will use a postgres database, gunicorn to serve the website, and nginx to 'reverse proxy' requests from outside to gunicorn. It makes life easier if you also use PyCharm, and supervisor to manage gunicorn.
- You will want to buy a VPS, which is a (virtual) server on which you can install whatever you want. Make sure not to buy something called 'shared hosting' as it probably means you can only upload static files. At the moment a VPS can be as cheap as five euros a month.
- If, when buying a VPS, you can choose between a pre-installed or ISO-VPS, choose the ISO-VPS, i.e. choose the option with the most freedom (avoid 'time-saving' options).
- For this tutorial we will assume you have chosen the latest Ubuntu version, this tutorial is tested with Ubuntu 16.04 and 18.04.
- If you don't have a domain yet, you can probably buy it via the same company as you bought the VPS. If you have one, you can probably transfer the management of it to that company. You could also leave it as you have it, and just point the DNS to the ip address of the VPS.
From now on we assume that the server is already up and running and that you can execute sudo
commands via SSH, for example using ssh [email protected]
in bash.
If not, for example because you have walked through the Ubuntu installation yourself, make sure you install openssh-server
(with sudo apt install openssh-server
).
If you cannot access because you have no root password, you should have created an other user, say eve
.
Then you should be able to login with eve
instead of root
.
Run sudo apt update
and sudo apt upgrade
before anything.
Random remark: note that apt
is the user command, apt-get
is the low-level command useful for scripting.
- If you already have a second user besides root, skip this step. Otherwise, log in via SSH with the root user. Create a new user with your name with
adduser eve
. Give her root permissions withusermod -aG sudo eve
. Impersonate her withsudo su - eve
. Note you have to do this and the next three instructions (adding a public key) for every user you want to give access to the server. - Set up login with a key pair, if needed on your local computer generate keys with
ssh-keygen -t rsa
, otherwise reuse the key you have. View your public key by executing (locally) in bashcat ~/.ssh/id_rsa.pub
and copy all of the output. - To put it on the server, use
mkdir ~/.ssh
to create the directory,chmod 700 ~/.ssh
to change permissions,nano ~/.ssh/authorized_keys
(nano is a text editor, you can also use vim) to put the key in this file andchmod 600 ~/.ssh/authorized_keys
. - Test that it works by opening a new bash window and check that you can login with eve without needing to enter your password.
- Install a firewall,
sudo apt install ufw
. - Allow SSH (22), Postgres (5432), http (80) and https (443) and other things you can think of:
sudo ufw allow xxx
wherexxx
is a port number. sudo ufw enable
and check withsudo ufw status
.- Before closing your existing connection to the server, check if you can login to a new session! Otherwise you could lock yourself out.
- Besides a firewall, you also want to protect your server against bots that repeatedly try to guess your password, for that you can install
sudo apt install -y fail2ban
. - Create a new file
sudo nano /etc/fail2ban/jail.local
and put into it
[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
- Start and enable (start on boot) the service with
sudo systemctl start fail2ban
andsudo systemctl enable fail2ban
.
-
Add your server to PyCharm in Settings | Build, Execution, Deployment | Deployment, click on the plus icon, choose SFTP, enter the IP address of your server in SFTP host, specify user name, choose as authentication Key Pair and specify your key file, for Windows probably in
C:\Users\username\.ssh\id_rsa
or on Linux/home/username/.ssh/id_rsa
. Also, if not already done, specify web server url ashttp://myipadress
.- If you get the error 'Keypair is corrupt or has unknown format', then try selecting OpenSSH config as Authentication instead.
- If you then still cannot make a connection, use password authentication.
-
Make the server the default one by clicking an icon a few to the right of the 'plus' you used to add the server. When the server name becomes bold, you have set it as default.
-
Go to Settings | Tools | SSH Terminal and select the server as Deployment server.
-
You should now be able to ssh into your server with Tools | Start SSH Session (assigning a shortcut to this is a good idea: go to Settings | Keymap, search for 'start ssh' and add a shortcut, e.g. Alt+S).
-
If needed (and if you already want your website domain to point to this VPS) point your (sub)domain to the ip address of your server, probably in the settings of the provider where you registered the domains. This can take a few hours to take effect.
-
Install the packages we need with
sudo apt install python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx
.
- Start a postgres session with
sudo -u postgres psql
. CREATE DATABASE mysite_db;
CREATE USER mysite WITH PASSWORD '1234';
ALTER ROLE mysite SET client_encoding TO 'utf8';
ALTER ROLE mysite SET default_transaction_isolation TO 'read committed';
- Now check if the timezone in
settings/base.py
is correct, if not you can modify it to for exampleEurope/Amsterdam
. ThenALTER ROLE mysite SET timezone TO 'Europe/Amsterdam';
GRANT ALL PRIVILEGES ON DATABASE mysite_db TO mysite;
- If you want to use tests which use the database (recommended, see https://docs.djangoproject.com/en/2.2/topics/testing/overview/) you also need to run
ALTER USER mysite CREATEDB;
\q
to exit- Update the production database settings in
mysite/settings/production.py
- Generate a new secret key to enter in the same file. For example using PyCharm's Python console with
from django.utils.crypto import get_random_string
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^*(-_=+)'
print(get_random_string(50, chars))
- Make sure to keep this file secret. Also don't forget to check
DEBUG = False
in here. - While you're there, also add your website domain to
ALLOWED_HOSTS
in the base settings. Be sure to add your server's ip address as well if you want to debug with that! - Lastly, to enable connection from outside, you need to add to
/etc/postgresql/9.5/main/postgresql.conf
just at the end,listen_addresses = '*'
and in/etc/postgresl/9.5/main/pg_hba.conf
you need to addhost all all 0.0.0.0/0 md5
. - Restart postgres with
sudo /etc/init.d/postgresql restart
. - If at any time you get the error
psycopg2.OperationalError: could not connect to server: Connection refused
Is the server running on host "x.x.x.x" and accepting
TCP/IP connections on port 5432?
then the previous steps could be the problem (or your firewall is still blocking 5432, of course, or ... ).
- Check that you're running the python 3 version of pip with
pip -V
. If not, trypip3 -V
. If that works, you have to substitutepip
withpip3
from now on. - Similarly, check if you're using python 3 with
python -V
, and if you needpython3
then substitute, or do something else to fix it.- If you need Python 3.7 but the Linux distribution you have doesn't have it, you can follow instructions at https://serverfault.com/a/919064/437404 because this will also install pip. Now you will need to use
python3.7
andpip3.7
instead ofpython
andpip
in any command. Do not remove the default installed python 3.5.
- If you need Python 3.7 but the Linux distribution you have doesn't have it, you can follow instructions at https://serverfault.com/a/919064/437404 because this will also install pip. Now you will need to use
- Install the package with
pip install virtualenv
.- If this fails because of permissions, ensure ownership of python to install packages with
sudo chown -R eve:eve /usr/local
.
- If this fails because of permissions, ensure ownership of python to install packages with
- I happen to want my virtual environments in
/opt/
so I docd /opt
.- (if you are the only person who is ever going to work on it, putting it in
/home/username
would avoid some permission problems)
- (if you are the only person who is ever going to work on it, putting it in
- Make sure you create a virtual environment with the latest python you installed! Creating a virtual environment appears to be very much liable to change with python versions, so if the following steps don't work just search for instructions for your specific environment.
- Install venv with
sudo apt install python3-venv
. - I had python 3.5 installed, checked with
python3.5 -V
. Then you should be able to create a virtual environment withsudo python3.5 -m venv mysite_env
. Same for 3.7 etc.- If that does not work, I once solved that under python 3.6 by doing
sudo python3.6 -m venv --without-pip mysite_env
,source mysite_env/bin/activate
,sudo apt install curl
, then found out curl doesn't run as sudo so didsudo bash -c "curl https://bootstrap.pypa.io/get-pip.py | python"
thendeactivate
.
- If that does not work, I once solved that under python 3.6 by doing
- Every time you want to do something in your virtual environment, activate it with (change
mysite
to your website name)source mysite_env/bin/activate
. Do so, now. - Check with
python -V
for correct python version, andpip -V
for correct pip version in the correct location (should be in the virtual environment).- If you did set the virtual environment up without pip, download it with
wget https://bootstrap.pypa.io/get-pip.py
, install withpython3.6 get-pip.py
.
- If you did set the virtual environment up without pip, download it with
- Check with
which pip
andwhich python
that everything points inside your virtual environment. If you do need to use for examplepython3.6
instead ofpython
, remember that or fix thepython
command to avoid mistakes.
cd mysite_env
andsudo mkdir mysite
, then correct ownership withsudo chown -R eve:eve /opt/
.- In PyCharm, go to the deployment settings of your server as before and edit Root path to the directory you just created, so
/opt/mysite_env/mysite
. Under Mappings, specify/
as Deployment Path. - Under Options (click on the arrow next to Deployment in the left menu) you can specify to upload changes automatically or if you hit
CTRL+S
. Click Ok. - You don't want to upload everything you have locally to the server, for example it makes no sense to upload files in your local virtual environment. To exclude these paths, go to Settings | Build, Execution, Deployment | Deployment | Excluded Paths and add them, by hitting the plus icon and choosing Local Path.
- You may notice that if you try to exclude a folder which is marked as Excluded in PyCharm (not excluded from upload, but excluded like not belonging to the project, which you can see as the folder is marked red) then you cannot exclude it from deployment and you will get a message saying 'Local path is out of project'. (Note, however, that sometimes you can exclude it even though you get the warning.) This is a bug in PyCharm, vote for it at https://youtrack.jetbrains.com/issue/WI-7367. To exclude these folders for deployment, first unmark them as excluded by right-clicking on them and choosing Mark Directory as | Cancel Exclusion. Now you can exclude them for deployment, and mark them again as excluded after you did that (PyCharm will give an error in the settings but it will still work).
- Now theoretically you should be able to upload everything by hitting Ctrl+S, but we have found that often this does not work. A more reliable way is to first select your project folder in the project view on the left (probably the topmost folder) and selecting Tools | Deployment | Upload to ..., or using the default shortcut Ctrl+Alt+Shift+X and choosing your server.
- If you need
msqlclient
, first installsudo apt install python3.5-dev libmysqlclient-dev
which are needed for themysqlclient
package. - If you didn't remember, check with
which python
(with virtualenv activated) where your python hides, then in PyCharm go to Settings | Project ... | Project Interpreter and add a new remote one by selecting the gear icon at the top right. - Select SSH Interpreter in the left menu, then Existing Server Configuration, select as Deployment Configuration your server and Move Deployment Server, then select the right path to your python of the virtual environment. Also take care to change the Sync paths, of which the remote path probably should be
/
- but make sure to check the Deployment path under Mappings afterwards and reset it to/
. - PyCharm should warn you about some dependencies from requirements.txt not being installed, do that. Probably PyCharm will also install helper files which can take a long time.
- Make sure you have the remote python selected as interpreter, (you can also check for package updates there), now you can just like before hit Tools | Run Manage.py Task and run
makemigrations
andmigrate
but now both with production settings: somakemigrations --settings=mysite.settings.production
and also formigrate
. - If that fails, try running these tasks manually, so go to
/opt/mysite_env/mysite
and runpython manage.py makemigrations
and same formigrate
. - If needed, create superuser just as with local setup,
createsuperuser --username myname
. - Run the manage.py task
collectstatic --settings=mysite.settings.production
to gather static files for nginx to serve. - Double-check that Deployment Settings | Mappings | Deployment Path is set to
/
, PyCharm sometimes resets this. If this is wrong, uploading files won't work properly.
- We will setup gunicorn such that nginx will be able to redirect requests to gunicorn which is bound to the Django server.
- Copy the contents of the
gunicorn_start
script in/opt/mysite_env/bin/
(after changing all the paths, of course), create file withnano /opt/mysite_env/bin/gunicorn_start
(do not use sudo or FileZilla here) and make sure it has executable permissions withsudo chmod u+x /opt/mysite_env/bin/gunicorn_start
.
- We use supervisor to manage the starting and stopping of gunicorn. If your server would crash or for whatever reason is restarted, this makes sure to automatically start your website too.
- Install with
sudo apt install supervisor
. - Put the file
mysite.conf
insudo nano /etc/supervisor/conf.d/mysite.conf
, make sure it has executable permissions just like with the gunicorn start script. - Every time after you change such a supervisor config file, you have to do
sudo supervisorctl reread
andsudo supervisorctl update
. Do this now. I gathered things to remember like this below. - You can manually restart with
sudo supervisorctl restart mysite
.
- Install with
sudo apt install nginx
- If you do not have the folders
/etc/nginx/sites-available/
and/etc/nginx/sites-enabled
, you can create them and you also need to include them by putting in/etc/nginx/nginx.conf
at the bottom of thehttp
block the following:
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
and remove the server
block in the http
block.
-
Edit the content of the
nginx-config
into the filesudo nano /etc/nginx/sites-available/mysite
. We will set up https later. -
Enable your site by making the symbolic link
sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/mysite
-
Remove the symbolic link to the default config,
sudo rm /etc/nginx/sites-available/default
andsudo rm /etc/nginx/sites-enabled/default
-
Create empty log file
mkdir /opt/mysite_env/mysite/logs/
andtouch /opt/mysite_env/mysite/logs/nginx-access.log
. -
Make the socket directory with
mkdir /opt/mysite_env/mysite/run/
. -
Create an empty socket file
touch /opt/mysite_env/mysite/run/gunicorn.sock
in the same way, and alsosudo chmod 666 /opt/mysite_env/mysite/run/gunicorn.sock
. If at any time you get the error that this is not a socket, remove it. A socket is just a text file, with the great usefulness of enabling nginx to talk to gunicorn in a language that they both understand. -
Make sure the lines in the nginx config which point to the ssl certificates are commented.
-
Test the syntax of your nginx config file with
sudo nginx -t
and fix any. -
Make sure you have your ip and domain (without the
http(s)://
prefix, but both with and without thewww.
prefix) in allowed hosts in your Django settings file. -
If you will set up https later, you can skip the https section below, but make sure to start nginx in the section after that.
Because it's not much work and free, just do it. You need to have your domain pointing to your ip address already.
- You can get an ssl certificate for free, for example from Let's Encrypt. In that case, just follow their install guide.
- When running certbot, when it asks for domains provide it both with and without the
www.
prefix. If you need to choose, you are not serving files out of a directory on the server. - Either choose in the certbot setup to redirect http to https (in which case you need to add your domain without
www.
prefix toserver_name
in the largestserver
block) or do this yourself by uncommenting the https-related parts in thenginx-config
, marked with# ---- HTTPS setup start/end ----
, and remove thelisten 80;
line. - In any case, make sure the main server block has only one
listen ...
line, oneserver_name ...
line etc. - Possibly you need to
sudo fuser -k 80/tcp
to clean things up after setting up https.
- Start nginx with
sudo service nginx start
. Note that if you getservice: command not found
then whenever this tutorial usesservice
like this you need to usesudo systemctl start nginx
instead. - In the future, restart nginx with
sudo service nginx restart
. - In case that fails, check the logs at
tail /var/log/long.err.log
ortail /var/log/long.out.log
to view the error. - Try to reach your website. If it doesn't work, try setting
DEBUG = True
in settings and thensudo supervisorctl restart mysite
, reload page. - If it still does not work, restart both nginx (see above) and gunicorn with
sudo supervisorctl restart mysite
Now go in your browser to your ip address or domain and you should see your website. If not, check the logs for errors (see below).
- Check the certbot user guide to see if you got automated renewal out of the box. For Ubuntu version >= 17.10, this should be okay. This means that there is a cronjob that runs twice a day to renew all certificates that are about to expire. All we have to do is restart the nginx server after each renewal.
- Run
sudo vim /etc/cron.d/certbot
and append--renew-hook "service nginx restart"
so that the last line looks like0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew --renew-hook "service nginx restart
. - Run
sudo certbot renew --dry-run
, this should simulate renewal. If this succeeds without errors, everything should be okay.
To renew certificates manually, do sudo certbot renew
.
After making changes to Django files, run sudo supervisorctl restart mysite
.
After making changes to Django models, in PyCharm start Tools | Run Manage.py Task and run makemigrations --settings=mysite.settings.production
and migrate --settings=mysite.settings.production
(or from the command line, python3.6 manage.py makemigrations --settings=...
)
After making changes to static files run as manage.py task collectstatic
. If run from the command line, I think you need to activate the virtual environment first.
Every time after you change a supervisor config file in /etc/supervisor/conf.d/mysite.conf
, you have to do sudo supervisorctl reread
and sudo supervisorctl update
.
Restart supervisor with sudo supervisorctl restart mysite
.
After changing nginx config files in /etc/nginx/sites-available/mysite
, test syntax with sudo nginx -t
and run sudo service nginx restart
.
General logs are viewed with tail /var/log/long.err.log
or tail /var/log/long.out.log
, and the nginx log can be found with tail /opt/mysite_env/mysite/logs/nginx-error.log
as specified in the nginx config file.
If you get an error and nothing appears in the logs, try setting DEBUG = True
in settings and then sudo supervisorctl restart mysite
, reload page.