Tutorial

How To Deploy a Local Django App to a VPS

Published on September 20, 2013
Default avatar

By Stephen Tippets

How To Deploy a Local Django App to a VPS

Prerequisites


This tutorial assumes that you have already set up your virtual private server with your operating system of choice (Debian 7 was used for this tutorial; Ubuntu will also work). If you have not already done so, you can follow this tutorial. Before you begin, make sure your cloud server is properly configured to host Django applications with a database server, web server, and virtualenv already installed. If you have not already done this, please follow steps 1 - 6 about setting up a server for Django.

Step One: Update Packages


Before doing anything, it is always good practice to ensure all of your packages managed through apt, or whatever your package manager of choice is, are up to date. You can do this by connecting to your VPS via SSH and running the following commands:

sudo apt-get update
sudo apt-get upgrade

The first command downloads any updates for packages managed through apt-get. The second command installs the updates that were downloaded. After running the above commands, if there are updates to install you will likely be prompted to indicate whether or not you want to install these updates. If this happens, just type “y” and then hit “enter” when prompted.

Step Two: Set Up Your Virtualenv


If you completed the prerequisites, this should already be set up and you can skip this step.

Now we need to set up our virtualenv where our project files and Python packages will live. If you don’t use virtualenv, then simply create the directory where your Django project will live and move to step three.

To create your virtualenv run the following command. Remember to replace the path with the desired path of your project project on the virtual private server:

virtualenv /opt/myproject

Now that you have your virtualenv set up, you may activate your virtualenv and install Django and any other Python packages you may need using pip. Below is an example of how to activate your virtualenv and use pip to install Django:

source /opt/myproject/bin/activate
pip install django

Now we’re ready to create a database for our project!

Step Three: Create a Database


This tutorial assumes you use PostgreSQL as your database server. If not, you will need to check out documentation on how to create a database for your database server of choice.

To create a database with PostgreSQL start by running the following command:

sudo su - postgres

Your terminal prompt should now say “postgres@yourserver”. If so, run this command to create your database, making sure to replace “mydb” with your desired database name:

createdb mydb

Now create your database user with the following command:

createuser -P

You will now be met with a series of six prompts. The first will ask you for the name of the new user (use whatever name you would like). The next two prompts are for your password and confirmation of password for the new user. For the last three prompts, simply enter “n” and hit “enter.” This ensures your new user only has access to what you give it access to and nothing else. Now activate the PostgreSQL command line interface like so:

psql

Finally, grant this new user access to your new database with the following command:

GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser;

You are now set with a database and a user to access that database. Next, we can work on configuring our web server to serve up our static files!

Step Four: Configure Your VPS

We need to create a new configuration file for our site. This tutorial assumes you are using NGINX as your cloud server. If this is not the case, you will need to check the documentation for your web server of choice in order to complete this step.

For NGINX, run the following command to create and edit your site’s web server configuration file, making sure to replace “myproject” at the end of the command with the name of your project:

sudo nano /etc/nginx/sites-available/myproject

Now enter the following lines of code into the open editor:

server {
    server_name yourdomainorip.com;

    access_log off;

    location /static/ {
        alias /opt/myenv/static/;
    }

    location / {
        proxy_pass http://127.0.0.1:8001;
        proxy_set_header X-Forwarded-Host $server_name;
        proxy_set_header X-Real-IP $remote_addr;
        add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';
    }
}

Save and exit the file. The above configuration has set NGINX to serve anything requested at yourdomainorip.com/static/ from the static directory we set for our Django project. Anything requested at yourdomainorip.com will proxy to localhost on port 8001, which is where we will tell Gunicorn (or your app server of choice) to run. The other lines ensure that the hostname and IP address of the request get passed on to Gunicorn. Without this, the IP address of every request becomes 127.0.0.1 and the hostname is just your VPS hostname.

Now we need to set up a symbolic link in the /etc/nginx/sites-enabled directory that points to this configuration file. That is how NGINX knows this site is active. Change directories to /etc/nginx/sites-enabled like this:

cd /etc/nginx/sites-enabled

Once there, run this command:

sudo ln -s ../sites-available/myproject

Now restart NGINX with the command below and you should be set:

sudo service nginx restart

You may see the following error upon restart:

server_names_hash, you should increase server_names_hash_bucket_size: 32

You can resolve this by editing ’ /etc/nginx/nginx.conf ’

Open the file and uncomment the following line:

server_names_hash_bucket_size 64;

Now let’s get our project files pushed up to our droplet!

Step Five: Move Local Django Project to Droplet


We have several options here: FTP, SFTP, SCP, Git, SVN, etc. We will go over using Git to transfer your local project files to your virtual private server.

Find the directory where you set up your virtualenv or where you want your project to live. Change into this directory with the following command:

cd /opt/myproject

Once there, create a new directory where your project files will live. You can do this with the following command:

mkdir myproject

It may seem redundant to have two directories with the same name; however, it makes it so that your virtualenv name and project name are the same.

Now change into the new directory with the following command:

cd myproject

If your project is already in a Git repo, simply make sure your code is all committed and pushed. You can check if this is the case by running the following command locally on your computer in a terminal (for Mac) or a command prompt (for PC):

git status

If you see no files in the output then you should be good to go. Now SSH to your droplet and install Git with the following command:

sudo apt-get install git

Make sure to answer yes to any prompts by entering “y” and hitting “enter.” Once Git is installed, use it to pull your project files into your project directory with the following command:

git clone https://webaddressforyourrepo.com/path/to/repo .

If you use Github or Bitbucket for Git hosting there is a clone button you can use to get this command. Be sure to add the “.” at the end. If we don’t do this, then Git will create a directory with the repo name inside your project directory, which you don’t want.

If you don’t use Git then use FTP or another transfer protocol to transfer your files to the project directory created in the steps above.

Now all that’s left is setting up your app server!

Step Six: Install and Configure App Server


If you completed the prerequisites, this should already be set up and you can skip this step.

Now we need to install our app server and make sure it listens on port 8001 for requests to our Django app. We will use Gunicorn in this example. To install Gunicorn, first activate your virtualenv:

source /opt/myproject/bin/activate

Once your virtualenv is active, run the following command to install Gunicorn:

pip install gunicorn

Now that Gunicorn is installed, bind requests for your domain or ip to port 8001:

gunicorn_django --bind yourdomainorip.com:8001

Now you can hit “ctrl + z” and then type “bg” to background the process (if you would like). More advanced configuration and setup of Gunicorn can be found in step nine of this tutorial.

Now you’re ready for the final step!

Step Seven: Configure Your App


The final step is to configure your app for production. All of the changes we need to make are in your “settings.py” file for your Django project. Open this file with the following command:

sudo nano /opt/myproject/myproject/settings.py

The path to your settings file may differ depending on how your project is set up. Modify the path in the command above accordingly.

With your settings file open, change the DEBUG settings to False:

DEBUG = False

This will make it so that errors will show up to users as 404 or 500 error pages, rather than giving them a stack trace with debug information.

Now edit your database settings to look like the following, using your database name, user, and password instead of the ones shown below:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.psycopg2', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'mydb',                      # Or path to database file if using sqlite3.
        # The following settings are not used with sqlite3:
        'USER': 'myuser',
        'PASSWORD': 'password',
        'HOST': '',                      # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
        'PORT': '',                      # Set to empty string for default.
    }
}

Now edit your static files settings:

STATIC_ROOT = '/opt/myproject/static/'

STATIC_URL = '/static/'

Save and exit the file. Now all we need to do is collect our static files. Change into the directory where your “manage.py” script is and run the following command:

python manage.py collectstatic

This command will collect all the static files into the directory we set in our settings.py file above.

And that’s it! You’ve now got your app deployed to production and ready to go.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Stephen Tippets

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
10 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Andrew SB
DigitalOcean Employee
DigitalOcean Employee badge
April 8, 2014

@ekonstantinidis

With more recent versions of gunicorn (greater than version 18.0), you should run your application with the wsgi.py file that should be apart of your project. The equivalent version on the command run in this article would be:

gunicorn --bind yourdomainorip:8001 myproject.wsgi:application

For further reading, see:

http://docs.gunicorn.org/en/18.0/news.html#id1 https://docs.djangoproject.com/en/1.4/howto/deployment/wsgi/gunicorn/

Let me know if you don’t figure it out!

@astarr Once again you’re right! I managed to get mysql working. The only issue left is that static files won’t show up.

If I access them directly (http://188.xxx.xxx.93/static/css/bootstrap.css) they show up. But when running my application using gunicorn, I can see in the source code that it tried to find them using the port (http://188.xxx.xxx.93:8001)/static/css/bootstrap.css) so they won’t show up.

I guess it has to do with settings.py?

Thanks for the tutorial! It was mostly very easy to follow.

I’ve followed all the steps but when I visit my domain the website is not live. When I try to visit the IP address of my Digital Ocean droplet from within my web browser it says ‘This site can’t be reached. xxx.xxx.xxx.xxx took too long to respond.’

Does anyone know why this is?

Thanks!

Como configuro supervisor?? Ayuda !!

I have followed the tutorial properly and am able to host my Django app. However, I have two issues:

  1. My static files are in /opt/app/appname/website/app_main/static, but the static files are not loading. tail /var/log/nginx/error.log show file is loaded from somewhere else entirely! /home/django/django_project/django_project/static/js/authorizedCap.js I’ve used collectstatic but to no avail.

  2. Website is being made available on www.mydomain.com:8001 I want it to be available on www.mydomain.com. How do I change that?

in step six i gave this command : $ gunicorn --bind campusballot.com:8001 campusballot.wsgi:application

I got this error:

(myenv)root@campusballot:/# gunicorn --bind campusballot.com:8001 campusballot.wsgi:application
[2015-03-26 11:11:07 +0000] [13452] [INFO] Starting gunicorn 19.3.0
[2015-03-26 11:11:07 +0000] [13452] [INFO] Listening at: http://127.0.1.1:8001 (13452)
[2015-03-26 11:11:07 +0000] [13452] [INFO] Using worker: sync
[2015-03-26 11:11:07 +0000] [13457] [INFO] Booting worker with pid: 13457
[2015-03-26 11:11:07 +0000] [13457] [ERROR] Exception in worker process:
Traceback (most recent call last):
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py", line 507, in spawn_worker
    worker.init_process()
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/workers/base.py", line 118, in init_process
    self.wsgi = self.app.wsgi()
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/base.py", line 67, in wsgi
    self.callable = self.load()
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 65, in load
    return self.load_wsgiapp()
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 52, in load_wsgiapp
    return util.import_app(self.app_uri)
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/util.py", line 355, in import_app
    __import__(module)
ImportError: No module named campusballot.wsgi
Traceback (most recent call last):
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py", line 507, in spawn_worker
    worker.init_process()
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/workers/base.py", line 118, in init_process
    self.wsgi = self.app.wsgi()
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/base.py", line 67, in wsgi
    self.callable = self.load()
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 65, in load
    return self.load_wsgiapp()
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 52, in load_wsgiapp
    return util.import_app(self.app_uri)
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/util.py", line 355, in import_app
    __import__(module)
ImportError: No module named campusballot.wsgi
[2015-03-26 11:11:07 +0000] [13457] [INFO] Worker exiting (pid: 13457)
Traceback (most recent call last):
  File "/opt/myenv/bin/gunicorn", line 11, in <module>
    sys.exit(run())
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 74, in run
    WSGIApplication("%(prog)s [OPTIONS] [APP_MODULE]").run()
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/base.py", line 189, in run
    super(Application, self).run()
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/base.py", line 72, in run
    Arbiter(self).run()
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py", line 174, in run
    self.manage_workers()
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py", line 477, in manage_workers
    self.spawn_workers()
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py", line 541, in spawn_workers
    time.sleep(0.1 * random.random())
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py", line 214, in handle_chld
    self.reap_workers()
  File "/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py", line 459, in reap_workers
    raise HaltServer(reason, self.WORKER_BOOT_ERROR)

I used this command : gunicorn --bind campusballot.com:8001 campusballot.wsgi:application

i step 6, i got this error :

(myenv)root@campusballot:/# gunicorn --bind campusballot.com:8001 campusballot.wsgi:application [2015-03-26 11:09:32 +0000] [13444] [INFO] Starting gunicorn 19.3.0 [2015-03-26 11:09:32 +0000] [13444] [INFO] Listening at: http://127.0.1.1:8001 (13444) [2015-03-26 11:09:32 +0000] [13444] [INFO] Using worker: sync [2015-03-26 11:09:32 +0000] [13449] [INFO] Booting worker with pid: 13449 [2015-03-26 11:09:32 +0000] [13449] [ERROR] Exception in worker process: Traceback (most recent call last): File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py”, line 507, in spawn_worker worker.init_process() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/workers/base.py”, line 118, in init_process self.wsgi = self.app.wsgi() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/base.py”, line 67, in wsgi self.callable = self.load() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py”, line 65, in load return self.load_wsgiapp() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py”, line 52, in load_wsgiapp return util.import_app(self.app_uri) File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/util.py”, line 355, in import_app import(module) ImportError: No module named campusballot.wsgi Traceback (most recent call last): File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py”, line 507, in spawn_worker worker.init_process() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/workers/base.py”, line 118, in init_process self.wsgi = self.app.wsgi() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/base.py”, line 67, in wsgi self.callable = self.load() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py”, line 65, in load return self.load_wsgiapp() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py”, line 52, in load_wsgiapp return util.import_app(self.app_uri) File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/util.py”, line 355, in import_app import(module) ImportError: No module named campusballot.wsgi [2015-03-26 11:09:32 +0000] [13449] [INFO] Worker exiting (pid: 13449) Traceback (most recent call last): File “/opt/myenv/bin/gunicorn”, line 11, in <module> sys.exit(run()) File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py”, line 74, in run WSGIApplication(“%(prog)s [OPTIONS] [APP_MODULE]”).run() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/base.py”, line 189, in run super(Application, self).run() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/base.py”, line 72, in run Arbiter(self).run() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py”, line 174, in run self.manage_workers() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py”, line 477, in manage_workers self.spawn_workers() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py”, line 541, in spawn_workers time.sleep(0.1 * random.random()) File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py”, line 214, in handle_chld self.reap_workers() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py”, line 459, in reap_workers raise HaltServer(reason, self.WORKER_BOOT_ERROR) gunicorn.errors.HaltServer: <HaltServer ‘Worker failed to boot.’ 3> (myenv)root@campusballot:/# clear (myenv)root@campusballot:/# gunicorn --bind campusballot.com:8001 campusballot.wsgi:application [2015-03-26 11:11:07 +0000] [13452] [INFO] Starting gunicorn 19.3.0 [2015-03-26 11:11:07 +0000] [13452] [INFO] Listening at: http://127.0.1.1:8001 (13452) [2015-03-26 11:11:07 +0000] [13452] [INFO] Using worker: sync [2015-03-26 11:11:07 +0000] [13457] [INFO] Booting worker with pid: 13457 [2015-03-26 11:11:07 +0000] [13457] [ERROR] Exception in worker process: Traceback (most recent call last): File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py”, line 507, in spawn_worker worker.init_process() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/workers/base.py”, line 118, in init_process self.wsgi = self.app.wsgi() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/base.py”, line 67, in wsgi self.callable = self.load() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py”, line 65, in load return self.load_wsgiapp() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py”, line 52, in load_wsgiapp return util.import_app(self.app_uri) File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/util.py”, line 355, in import_app import(module) ImportError: No module named campusballot.wsgi Traceback (most recent call last): File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py”, line 507, in spawn_worker worker.init_process() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/workers/base.py”, line 118, in init_process self.wsgi = self.app.wsgi() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/base.py”, line 67, in wsgi self.callable = self.load() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py”, line 65, in load return self.load_wsgiapp() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py”, line 52, in load_wsgiapp return util.import_app(self.app_uri) File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/util.py”, line 355, in import_app import(module) ImportError: No module named campusballot.wsgi [2015-03-26 11:11:07 +0000] [13457] [INFO] Worker exiting (pid: 13457) Traceback (most recent call last): File “/opt/myenv/bin/gunicorn”, line 11, in <module> sys.exit(run()) File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py”, line 74, in run WSGIApplication(“%(prog)s [OPTIONS] [APP_MODULE]”).run() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/base.py”, line 189, in run super(Application, self).run() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/app/base.py”, line 72, in run Arbiter(self).run() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py”, line 174, in run self.manage_workers() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py”, line 477, in manage_workers self.spawn_workers() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py”, line 541, in spawn_workers time.sleep(0.1 * random.random()) File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py”, line 214, in handle_chld self.reap_workers() File “/opt/myenv/local/lib/python2.7/site-packages/gunicorn/arbiter.py”, line 459, in reap_workers raise HaltServer(reason, self.WORKER_BOOT_ERROR) gunicorn.errors.HaltServer: <HaltServer ‘Worker failed to boot.’ 3> (myenv)root@campusballot:/#

OK, I think you have to be in the Django directory when you run gunicorn_django etc - though the tutorial doesn’t specify that.

I’m confused by these instructions. How does gunicorn actually know where to find the Django app? I can’t see anywhere where we’re actually pointing gunicorn at the Django app.

A couple of issues in step 3. Firstly like @brunoamaral, createuser -P doesn’t ask me for a username, just a password. Secondly, at the end of step 3 I can’t connect to the database :( Thirdly, at the end of step 3 I’m still the postgres Unix user, which isn’t ideal for continuing with the rest of the tutorial.

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel