A virtual machine to learn how to deploy the free Django Workshop tutorial.
The repository contains a configuration for a virtual machine. This configuration will install all necessary software during setup. It's a Debian system, the provisioning will done using Salt.
Salt will install and configure the following software:
- Apache and mod_wsgi for Python 3
- curl
- gettext
- Git
- memcached and python-memcached
- nano
- Node.js and npm
- Python 3.4
- PostgreSQL and psycopg2
- SQLite
- tree
- Vim
- wkhtmltopdf
- Dependencies to build lxml and pillow Python packages
To create the virtual machine you have to download and install Vagrant at first. Vagrant 1.8 has successfully been tested with this virtual machine.
You also have to install VirtualBox 5.0.
Then you need to set the path to the Django project you want to deploy. You do this by exporting the environment variable DJANGO_SOURCE
:
$ export DJANGO_SOURCE='../django-workshop/projects/cookbook-project'
Note
If you are on Windows please use the full path to the Django project. Also don't use quotes:
> set DJANGO_SOURCE=C:\Users\<username>\django-workshop\projects\cookbook-project
After that simply start the virtual machine using:
$ vagrant up
Once the virtual machine has been created and provisioned you can connect to it using Vagrant`s ssh
command:
$ vagrant ssh
----------------------------------------------------------------
Debian GNU/Linux 8.6 (jessie) built 2016-09-28
----------------------------------------------------------------
(venv)vagrant@django-deployment-vagrant:~$
The prompt is prefixed with (venv)
which is the name of the virtualenv that has automatically been activated for you on login.
If the directory where your wsgi.py
is located is not named cookbook
you need to update the Apache configuration. Otherwise you can skip this step. To do this run the following command, where you should replace myproject
at the end with the name of the directory where your wsgi.py
file is located:
(venv)vagrant@django-deployment-vagrant:~$ sudo salt-call state.apply apache \
pillar='{"project": {"django-config": "myproject"}}'
There will be a lot of output, important is the end which should look like this:
local:
----------
ID: apache2
Function: pkg.installed
Result: True
Comment: Package apache2 is already installed
Started: 23:30:25.489454
Duration: 632.087 ms
Changes:
----------
ID: /etc/apache2/conf-available/wsgi.conf
Function: file.managed
Result: True
Comment: File /etc/apache2/conf-available/wsgi.conf is in the correct state
Started: 23:30:26.127559
Duration: 15.482 ms
Changes:
----------
ID: /etc/apache2/sites-available/django.conf
Function: file.managed
Result: True
Comment: File /etc/apache2/sites-available/django.conf updated
Started: 23:30:26.143476
Duration: 15.175 ms
Changes:
----------
diff:
---
+++
@@ -32,9 +32,9 @@
# WSGI
WSGIDaemonProcess django.example.com python-path=/src:/home/vagrant/venv/lib/python3.4/site-packages processes=2 threads=15 display-name=%{GROUP} lang='en_US.UTF-8' locale='en_US.UTF-8'
WSGIProcessGroup django.example.com
- WSGIScriptAlias / /src/cookbook/wsgi.py
+ WSGIScriptAlias / /src/myproject/wsgi.py
- <Directory /src/cookbook>
+ <Directory /src/myproject>
<Files wsgi.py>
Require all granted
</Files>
----------
ID: apache2
Function: service.running
Result: True
Comment: Service restarted
Started: 23:30:26.319967
Duration: 1445.602 ms
Changes:
----------
apache2:
True
----------
ID: Enable headers module
Function: apache_module.enable
Name: headers
Result: True
Comment: headers already enabled.
Started: 23:30:27.767086
Duration: 0.748 ms
Changes:
----------
ID: libapache2-mod-wsgi-py3
Function: pkg.installed
Result: True
Comment: Package libapache2-mod-wsgi-py3 is already installed
Started: 23:30:27.768049
Duration: 2.415 ms
Changes:
----------
ID: /etc/apache2/conf-enabled/wsgi.conf
Function: file.symlink
Result: True
Comment: Symlink /etc/apache2/conf-enabled/wsgi.conf is present and owned by root:root
Started: 23:30:27.770629
Duration: 4.712 ms
Changes:
----------
ID: /etc/apache2/sites-enabled/000-default.conf
Function: file.symlink
Result: True
Comment: Symlink /etc/apache2/sites-enabled/000-default.conf is present and owned by root:root
Started: 23:30:27.775835
Duration: 1.519 ms
Changes:
Summary for local
------------
Succeeded: 8 (changed=2)
Failed: 0
------------
Total states run: 8
Total run time: 2.118 s
If Failed
has a value different from 0
, check if you have made any typos. Also take a close look at the error message(s). They usually contain a hint that helps you to find out the reason for the error.
After that you can connect to PostgreSQL. Use the password django
to authenticate:
(venv)vagrant@django-deployment-vagrant:~$ psql -h localhost -U django django
Password for user django:
psql (9.4.5)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.
django=> \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-------------+----------+----------+-------------+-------------+-----------------------
django | django | UTF8 | en_US.UTF8 | en_US.UTF8 |
news | django | UTF8 | en_US.UTF8 | en_US.UTF8 |
nobelprizes | django | UTF8 | en_US.UTF8 | en_US.UTF8 |
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(6 rows)
The django
PostgreSQL user has access to three databases:
django
news
nobelprizes
Now configure your Django project to use this database connection for all three databases by editing local_settings.py
as shown below. Also, don't forget to add the other settings DEBUG
, ALLOWED_HOSTS
and MEDIA_ROOT
.
The settings at the end of the file are security-related. They enable a few basic security settings. The setting SILENCED_SYSTEM_CHECKS
disables SSL-related checks as we're not using SSL for this deployment.
DEBUG = False
ALLOWED_HOSTS = ['127.0.0.1']
MEDIA_ROOT = '/home/vagrant/media'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'django',
'USER': 'django',
'PASSWORD': 'django',
'CONN_MAX_AGE': 600,
},
'newsdb': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'news',
'USER': 'django',
'PASSWORD': 'django',
'CONN_MAX_AGE': 600,
},
'addressdb': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'nobelprizes',
'USER': 'django',
'PASSWORD': 'django',
'CONN_MAX_AGE': 600,
},
}
WKHTMLTOPDF_CMD = 'wkhtmltopdf'
# Security
CSRF_COOKIE_HTTPONLY = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
SILENCED_SYSTEM_CHECKS = [
'security.W004',
'security.W008',
'security.W012',
'security.W016'
]
Note
Because we are running Apache inside a virtual machine and forwarding the port to our host machine ALLOWED_HOSTS
needs just the single value '127.0.0.1'
. A deployment on a real server would require something like 'example.com'
or 'www.example.com'
.
Also note that it's strongly recommended to set a different SECRET_KEY
for a production system.
Finally you have to run the following commands to deploy the Django project.
Change into the /src
directory (where Vagrant created a synched folder pointing at your project files):
(venv)vagrant@django-deployment-vagrant:~$ cd /src
Install all Python packages:
(venv)vagrant@django-deployment-vagrant:/src$ pip install -r requirements.txt
Note
psycopg2, the PostgreSQL database adapter for the Python, has already been installed into the virtual env.
If you don't have a requirements.txt
file, create one in your development environment using:
$ pip freeze > requirements.txt
Run the database migrations:
(venv)vagrant@django-deployment-vagrant:/src$ ./manage.py migrate
(venv)vagrant@django-deployment-vagrant:/src$ ./manage.py migrate --database=newsdb
Now run the deployment checks (no security issues should be identified):
(venv)vagrant@django-deployment-vagrant:/src$ ./manage.py check --deploy
Create a new superuser:
(venv)vagrant@django-deployment-vagrant:/src$ ./manage.py createsuperuser
Collect the static files into the directory /src/static_root
:
(venv)vagrant@django-deployment-vagrant:/src$ ./manage.py collectstatic
Also, you need to copy the directory for media files (uploads) to a different location. This is necessary so that the user www-data
, which is the user Apache uses, can write uploads to the disk. And unfortunately you can't transfer ownership of directories in a Vagrant share.
(venv)vagrant@django-deployment-vagrant:/src$ cp -R media /home/vagrant
If you don't have a media
directory, just create one in /home/vagrant
:
(venv)vagrant@django-deployment-vagrant:/src$ mkdir /home/vagrant/media
Then change the owner and group of the media
directory to www-data
:
(venv)vagrant@django-deployment-vagrant:/src$ sudo chown -R www-data: /home/vagrant/media
Finally restart the Apache web server:
(venv)vagrant@django-deployment-vagrant:/src$ sudo service apache2 stop
(venv)vagrant@django-deployment-vagrant:/src$ sudo service apache2 start
Now open http://127.0.0.1:8000 and visit your Django project!
If you want to understand how Apache and PostgreSQL have been configured to work with Django, take a look the following files:
/etc/apache2/conf-available/wsgi.conf
/etc/apache2/sites-available/django.conf
/etc/postgresql/9.4/main/pg_hba.conf
If the URL http://127.0.0.1:8000 does not work, check if Vagrant has auto-corrected the port forwarding for Apache to a different port. Use Vagrant's port
command to display the forwarded port. Example:
$ vagrant port --guest 80
8001
If you don't see anything in the browser or just an error message by Apache, here are a few things you can try to find out more.
Run the following command to see Apache status information:
(venv)vagrant@django-deployment-vagrant:/src$ sudo service apache2 status
Take a look at Apache`s global error log:
(venv)vagrant@django-deployment-vagrant:/src$ sudo less /var/log/apache2/error.log
Examine the Apache error log for the virtual host:
(venv)vagrant@django-deployment-vagrant:/src$ sudo less /var/log/apache2/django.example.com-error.log
Check if the media
directory has been copied and has the correct permissions:
(venv)vagrant@django-deployment-vagrant:/src$ ls -la /home/vagrant/media
total 20
drwxr-xr-x 3 www-data www-data 4096 Nov 16 16:43 .
drwxr-xr-x 6 vagrant vagrant 4096 Nov 16 16:52 ..
drwxr-xr-x 2 www-data www-data 4096 Nov 16 16:55 recipes
Everyone interacting in the django-deployment project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the PyPA Code of Conduct.