diff --git a/opentech/apply/funds/models/submissions.py b/opentech/apply/funds/models/submissions.py
index d99656cf08d6e01b1e83722aec482e27a3e9d92d..834d470753bd533f92a669680b52c76e6b37cf42 100644
--- a/opentech/apply/funds/models/submissions.py
+++ b/opentech/apply/funds/models/submissions.py
@@ -4,7 +4,7 @@ from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.contrib.postgres.fields import JSONField
 from django.core.exceptions import PermissionDenied
-from django.core.files.storage import default_storage
+from django.core.files.storage import DefaultStorage
 from django.core.serializers.json import DjangoJSONEncoder
 from django.db import models
 from django.db.models import ObjectDoesNotExist
@@ -39,6 +39,9 @@ from ..workflow import (
 )
 
 
+submission_storage = DefaultStorage()
+
+
 class JSONOrderable(models.QuerySet):
     json_field = ''
 
@@ -302,7 +305,7 @@ class ApplicationSubmission(WorkflowHelpers, BaseStreamForm, AbstractFormSubmiss
 
     def save_path(self, file_name):
         file_path = os.path.join('submissions', 'user', str(self.user.id), file_name)
-        return default_storage.generate_filename(file_path)
+        return submission_storage.generate_filename(file_path)
 
     def handle_file(self, file):
         # File is potentially optional
@@ -313,11 +316,11 @@ class ApplicationSubmission(WorkflowHelpers, BaseStreamForm, AbstractFormSubmiss
                 # file is not changed, it is still the dictionary
                 return file
 
-            saved_name = default_storage.save(filename, file)
+            saved_name = submission_storage.save(filename, file)
             return {
                 'name': file.name,
                 'path': saved_name,
-                'url': default_storage.url(saved_name)
+                'url': submission_storage.url(saved_name),
             }
 
     def handle_files(self, files):
diff --git a/opentech/settings/base.py b/opentech/settings/base.py
index 4171f5bcfbfbe35ba56711d780fc7999ae187219..11ceb1ed3812ac04c7f81f1db8b565a5f004a6d8 100644
--- a/opentech/settings/base.py
+++ b/opentech/settings/base.py
@@ -371,3 +371,24 @@ if 'REDIS_URL' in env:
     CELERY_BROKER_URL = env.get('REDIS_URL')
 else:
     CELERY_TASK_ALWAYS_EAGER = True
+
+
+# S3 configuration
+
+if 'AWS_STORAGE_BUCKET_NAME' in env:
+    DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
+    AWS_STORAGE_BUCKET_NAME = env['AWS_STORAGE_BUCKET_NAME']
+    AWS_QUERYSTRING_AUTH = False
+    AWS_S3_FILE_OVERWRITE = False
+
+    if 'AWS_S3_CUSTOM_DOMAIN' in env:
+        AWS_S3_CUSTOM_DOMAIN = env['AWS_S3_CUSTOM_DOMAIN']
+
+    if 'AWS_S3_SECURE_URLS' in env:
+        AWS_S3_SECURE_URLS = (
+            env['AWS_S3_SECURE_URLS'].strip().lower() == 'true'
+        )
+
+    INSTALLED_APPS += (
+        'storages',
+    )
diff --git a/requirements.txt b/requirements.txt
index 0ea9b0b33fa436dc7538579fffaa11c5336c4588..3b284508addca3548ed3a2af32a77ab97a8a04aa 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -33,3 +33,4 @@ whitenoise==2.0.4
 uwsgi==2.0.15
 ConcurrentLogHandler==0.9.1
 raven==6.9.0
+django-storages==1.6.6