├── backups
└── .gitkeep
├── nginx
├── ssl
│ └── .gitkeep
├── Dockerfile
├── setupconf.sh
├── nginx_nossl.conf
└── nginx_ssl.conf
├── backend
├── __init__.py
├── media
│ └── .gitkeep
├── apps
│ ├── __init__.py
│ ├── home
│ │ ├── __init__.py
│ │ ├── templates
│ │ │ └── home
│ │ │ │ └── index.html
│ │ ├── views.py
│ │ ├── urls.py
│ │ └── tests.py
│ └── testutils.py
├── conf
│ ├── __init__.py
│ ├── settings_test.py
│ ├── settings_prod.py
│ └── settings.py
├── .dockerignore
├── bin
│ ├── django.sh
│ ├── collectstatic.sh
│ ├── test.sh
│ ├── develop.sh
│ ├── install.sh
│ ├── start_production.sh
│ └── install_package.sh
├── requirements.txt
├── Dockerfile
├── manage.py
├── Dockerfile-production
├── gunicorn.conf.py
├── wsgi.py
├── templates
│ └── base.html
├── context_processors.py
└── urls.py
├── logs
└── prod log go here.txt
├── frontend
├── src
│ ├── frontend sources here.txt
│ ├── style
│ │ └── index.styl
│ ├── images
│ │ └── hamster.jpg
│ └── js
│ │ └── index.js
├── .babelrc
├── webpack
│ ├── dev-server.js
│ ├── webpack.config.development.js
│ ├── webpack.config.base.js
│ └── webpack.config.production.js
└── package.json
├── bin
├── start_production.sh
├── stop_production.sh
├── npm.sh
├── django.sh
├── develop.sh
├── pipinstall.sh
├── psql.sh
├── django_prod.sh
├── build_production.sh
├── test.sh
├── build_frontend.sh
├── backup.sh
├── deploy.sh
├── env.sh
├── restore.sh
└── init_db.sh
├── docker-compose.db.yml
├── .gitignore
├── docker-compose.yml
├── docker-compose.test.yml
├── docker-compose.override.yml
├── docker-compose.production.yml
└── README.md
/backups/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nginx/ssl/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/media/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/apps/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/conf/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/logs/prod log go here.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/apps/home/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/frontend sources here.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/apps/home/templates/home/index.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
--------------------------------------------------------------------------------
/backend/.dockerignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | __pycache__
3 | Dockerfile*
4 | pipcache
--------------------------------------------------------------------------------
/backend/bin/django.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ./bin/install.sh
3 | python3 manage.py "$@"
--------------------------------------------------------------------------------
/frontend/src/style/index.styl:
--------------------------------------------------------------------------------
1 | body
2 | background-color: green
3 | font-size: 70px
--------------------------------------------------------------------------------
/frontend/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015"],
3 | "plugins": ["transform-object-rest-spread"]
4 | }
--------------------------------------------------------------------------------
/backend/bin/collectstatic.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ./bin/install.sh
3 | python3 manage.py collectstatic --noinput
--------------------------------------------------------------------------------
/backend/bin/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ./bin/install.sh
3 | python3 manage.py test --noinput --liveserver=localhost:8082 "$@"
--------------------------------------------------------------------------------
/bin/start_production.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # start production server
3 |
4 | source bin/env.sh
5 | dcprod up -d
6 | echo "started"
--------------------------------------------------------------------------------
/bin/stop_production.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # stop production server
3 |
4 | source bin/env.sh
5 | dcprod stop
6 | echo "stopped"
--------------------------------------------------------------------------------
/backend/bin/develop.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ./bin/install.sh
3 | python3 manage.py migrate
4 | python3 manage.py runserver 0.0.0.0:8000
--------------------------------------------------------------------------------
/frontend/src/images/hamster.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/domasx2/docker-django-webpack-starter/HEAD/frontend/src/images/hamster.jpg
--------------------------------------------------------------------------------
/backend/apps/home/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 |
3 | def index(request):
4 | return render(request, 'home/index.html')
--------------------------------------------------------------------------------
/bin/npm.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #run npm command. use this to install new packages to dev
4 | source bin/env.sh
5 |
6 | dcdev run --rm frontend npm $@
--------------------------------------------------------------------------------
/backend/bin/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #install django dependencies
4 | mkdir -p pipcache
5 | pip3 install --cache-dir=/app/pipcache -r requirements.txt
--------------------------------------------------------------------------------
/backend/conf/settings_test.py:
--------------------------------------------------------------------------------
1 | from .settings import *
2 |
3 | STATIC_ASSETS_JSON = os.path.join(BASE_DIR, 'assets.json')
4 |
5 | WEBPACK_DEV_SERVER = False
6 |
--------------------------------------------------------------------------------
/bin/django.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #run django management command in development mode
4 | source bin/env.sh
5 |
6 | dcdev run --rm django ./bin/django.sh "$@"
--------------------------------------------------------------------------------
/bin/develop.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #start development server on :8000
4 |
5 | source bin/env.sh
6 |
7 | dcdev build
8 | ./bin/init_db.sh
9 | dcdev up
10 |
--------------------------------------------------------------------------------
/bin/pipinstall.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #install new pytho ndep via pip, add it to requirements.txt
4 | source bin/env.sh
5 |
6 | dcdev run --rm django ./bin/install_package.sh $@
--------------------------------------------------------------------------------
/backend/apps/home/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import url
2 | from apps.home.views import index as index_view
3 |
4 | urlpatterns = [
5 | url(r'^.*?', index_view, name='index')
6 | ]
--------------------------------------------------------------------------------
/bin/psql.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # open psql session to production db
3 |
4 | source bin/env.sh
5 |
6 | dcprod -f docker-compose.db.yml run --rm dbclient psql -h db -U $DB_USER $DB_NAME
7 |
--------------------------------------------------------------------------------
/docker-compose.db.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | dbclient:
4 | image: postgres:9.6
5 | depends_on:
6 | - db
7 | volumes:
8 | - "./backups:/backups"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *sublime*
2 | __pycache__
3 | *.pyc
4 | node_modules
5 | pipcache
6 | frontend/dist/*
7 | nginx/static/*
8 | nginx/ssl/*
9 | *.log
10 | backups/*.bak
11 | /backend/assets.json
12 |
--------------------------------------------------------------------------------
/bin/django_prod.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #run django management command in production mode
4 |
5 | source bin/env.sh
6 |
7 | dcprod run -e DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_PROD --rm django python3 manage.py $@
--------------------------------------------------------------------------------
/bin/build_production.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #builds production images
4 |
5 | source bin/env.sh
6 |
7 | ./bin/build_frontend.sh
8 | cp ./frontend/dist/assets.json ./backend/
9 | dcprod build
10 | rm ./backend/assets.json
--------------------------------------------------------------------------------
/backend/requirements.txt:
--------------------------------------------------------------------------------
1 | # postgres database adapater
2 | psycopg2==2.6.2
3 |
4 | # django framework itself
5 | Django==1.10.5
6 |
7 | #app server for prod
8 | gunicorn==19.6.0
9 |
10 | #selenium for e2e tests
11 | selenium==3.0.2
--------------------------------------------------------------------------------
/bin/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | source bin/env.sh
3 |
4 | if ! [[ $* == *--skipbuild* ]]; then
5 | ./bin/build_frontend.sh
6 | else
7 | echo "skipping frontend build..."
8 | fi
9 |
10 | dctest run --rm django ./bin/test.sh "$@"
--------------------------------------------------------------------------------
/nginx/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:1.9
2 |
3 | ADD ./nginx/ssl /etc/nginx/ssl
4 |
5 | ADD ./nginx/nginx_nossl.conf /etc/nginx/
6 | ADD ./nginx/nginx_ssl.conf /etc/nginx/
7 | ADD ./frontend/dist /static
8 | ADD ./nginx/setupconf.sh .
9 | RUN ./setupconf.sh
--------------------------------------------------------------------------------
/backend/bin/start_production.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE:-conf.settings_prod}
3 | echo djsettings:
4 | echo $DJANGO_SETTINGS_MODULE
5 | python3 manage.py migrate
6 | gunicorn -c /app/gunicorn.conf.py wsgi:application
--------------------------------------------------------------------------------
/backend/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.5
2 |
3 | ENV PYTHONUNBUFFERED 1
4 |
5 | RUN pip3 install --upgrade pip
6 | RUN pip3 install wheel
7 |
8 | RUN mkdir -p /app/static /tmp/logs/app
9 |
10 | VOLUME /app
11 | VOLUME /static
12 |
13 | WORKDIR /app
14 | EXPOSE 8000
--------------------------------------------------------------------------------
/backend/bin/install_package.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | package_name=$1
3 | if [[ -z $package_name ]]
4 | then
5 | echo "specify package name"
6 | exit
7 | fi
8 |
9 | pip3 install $package_name && pip3 freeze | grep -i $package_name | awk '{print "\n\n" $0}' >> ./requirements.txt
--------------------------------------------------------------------------------
/bin/build_frontend.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #compile frontend production build to frontend/dist
4 |
5 | source bin/env.sh
6 |
7 | dcdev build
8 | dcdev run --rm frontend npm run-script build
9 | dcdev run --rm -e DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_PROD django ./bin/collectstatic.sh
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | volumes:
3 | data:
4 | services:
5 | db:
6 | image: postgres:9.6
7 | environment:
8 | POSTGRES_USER: django
9 | POSTRES_PASSWORD: django
10 | volumes:
11 | - data:/var/lib/postgresql/data
--------------------------------------------------------------------------------
/backend/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "conf.settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/backend/Dockerfile-production:
--------------------------------------------------------------------------------
1 | FROM python:3.5
2 | ENV PYTHONUNBUFFERED 1
3 | RUN pip3 install --upgrade pip
4 | RUN mkdir -p /app
5 | WORKDIR /app
6 | COPY ./requirements.txt /app/requirements.txt
7 | RUN pip3 install -r requirements.txt
8 |
9 | COPY . /app/
10 | EXPOSE 8000
11 | CMD ./bin/start_production.sh
--------------------------------------------------------------------------------
/backend/apps/home/tests.py:
--------------------------------------------------------------------------------
1 | import time
2 | from apps.testutils import SeleniumTestCase
3 |
4 | class SampleTests(SeleniumTestCase):
5 |
6 | def test_something(self):
7 | self.driver.get(self.live_server_url)
8 | p = self.driver.find_element_by_css_selector('p')
9 | self.assertEqual(p.text, 'hello world')
--------------------------------------------------------------------------------
/backend/gunicorn.conf.py:
--------------------------------------------------------------------------------
1 | import multiprocessing
2 |
3 | bind = "0.0.0.0:8000"
4 | workers = multiprocessing.cpu_count() * 2 + 1
5 | errorlog = '/tmp/logs/gunicorn/access.log'
6 | loglevel = 'info'
7 | accesslog = '/tmp/logs/gunicorn/error.log'
8 |
9 | secure_scheme_headers = {'X-FORWARDED-PROTOCOL': 'ssl', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'}
--------------------------------------------------------------------------------
/bin/backup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # create a db backups to backups/
3 |
4 | source bin/env.sh
5 |
6 | BACKUP_FILE=django$1_$(date +'%Y_%m_%dT%H_%M_%S').bak
7 | docker-compose -f docker-compose.yml -f $DOCKER_CONFIG_PROD -f docker-compose.db.yml run --rm dbclient pg_dump -Fc -h db -U $DB_USER -d $DB_NAME -f /backups/$BACKUP_FILE
8 | echo "backup $BACKUP_FILE created"
--------------------------------------------------------------------------------
/backend/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for project project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "conf.settings_prod")
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/nginx/setupconf.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cd /etc/nginx
3 | rm nginx.conf
4 | if ls ssl/*.crt &> /dev/null; then
5 | mv nginx_ssl.conf nginx.conf
6 | CRT=$(ls ssl/*.crt | head -n 1)
7 | KEY=$(ls ssl/*.key | head -n 1)
8 | echo "using ssl, key=${KEY} crt=${CRT}"
9 | sed -i "s#ssl/server.crt#$CRT#g" /etc/nginx/nginx.conf
10 | sed -i "s#ssl/server.key#$KEY#g" /etc/nginx/nginx.conf
11 | else
12 | echo "not using ssl"
13 | mv /etc/nginx/nginx_nossl.conf /etc/nginx/nginx.conf
14 | fi
--------------------------------------------------------------------------------
/frontend/src/js/index.js:
--------------------------------------------------------------------------------
1 | import '../style/index.styl';
2 | import hamster_img_url from '../images/hamster.jpg';
3 |
4 | //import npm packages, node modules, other files here
5 |
6 | window.addEventListener('load', function() {
7 | const p = document.createElement('p');
8 | p.innerHTML = 'hello world';
9 | document.body.insertBefore(p, document.body.firstChild);
10 |
11 | const img = document.createElement('img');
12 | img.src = hamster_img_url;
13 | document.body.insertBefore(img, document.body.firstChild);
14 | });
--------------------------------------------------------------------------------
/bin/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #builds production images,
4 | #initializes database or creates db backup if it was initialized already
5 | #(re)starts prodcution containers
6 |
7 | source bin/env.sh
8 |
9 | #for init_db.sh
10 | export DOCKER_INIT_DB_CONFIG=$DOCKER_CONFIG_PROD
11 |
12 | if [ $(docker-compose -f docker-compose.yml -f $DOCKER_INIT_DB_CONFIG ps | grep dbdata | wc -l) == 0 ]; then
13 | ./bin/init_db.sh
14 | else
15 | ./bin/backup.sh
16 | fi
17 | ./bin/build_production.sh
18 | ./bin/stop_production.sh
19 | ./bin/start_production.sh
--------------------------------------------------------------------------------
/backend/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 |
3 |
4 |
5 |
6 |
7 |
8 | {% block "title" %}My application{% endblock %}
9 | {% if not WEBPACK_DEV_SERVER %}
10 |
11 | {% endif %}
12 |
13 |
14 | {% block "content" %}{% endblock %}
15 |
16 | {% if WEBPACK_DEV_SERVER %}
17 |
18 | {% else %}
19 |
20 | {% endif %}
21 |
22 |
--------------------------------------------------------------------------------
/backend/apps/testutils.py:
--------------------------------------------------------------------------------
1 | from django.test import LiveServerTestCase
2 | from selenium import webdriver
3 | from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
4 |
5 | class SeleniumTestCase(LiveServerTestCase):
6 |
7 | @classmethod
8 | def setUpClass(cls):
9 | super(SeleniumTestCase, cls).setUpClass()
10 | cls.driver = webdriver.Remote(
11 | command_executor='http://127.0.0.1:4444/wd/hub',
12 | desired_capabilities=DesiredCapabilities.CHROME)
13 | cls.driver.implicitly_wait(10)
14 |
15 | @classmethod
16 | def tearDownClass(cls):
17 | cls.driver.quit()
18 | super(SeleniumTestCase, cls).tearDownClass()
--------------------------------------------------------------------------------
/backend/context_processors.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | import os.path
3 | import json
4 |
5 | static_assets = None
6 |
7 | if not settings.WEBPACK_DEV_SERVER:
8 | assets_json_fname = os.path.join(settings.STATIC_ASSETS_JSON)
9 | try:
10 | with open(assets_json_fname, 'r') as f:
11 | static_assets = json.load(f)['main']
12 | except Exception:
13 | raise Exception('failed to read %s. was frontend built?' % assets_json_fname)
14 |
15 |
16 | #use by base.html to figure out what statics to load
17 | def static_resources(request):
18 | return {
19 | "WEBPACK_DEV_SERVER": settings.WEBPACK_DEV_SERVER,
20 | "static_assets": static_assets
21 | }
--------------------------------------------------------------------------------
/docker-compose.test.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | django:
4 | build: ./backend
5 | volumes:
6 | - ./backend:/app
7 | - ./backend/media:/media
8 | - ./frontend/dist:/static
9 | - ./frontend/dist/assets.json:/app/assets.json
10 | command: ./bin/test.sh
11 | ports:
12 | - "8082:8082"
13 | environment:
14 | DJANGO_SETTINGS_MODULE: ${DJANGO_SETTINGS_TEST}
15 | network_mode: "service:selenium"
16 | depends_on:
17 | - db
18 | - selenium
19 |
20 | selenium:
21 | image: selenium/standalone-chrome-debug:2.53.0
22 | ports:
23 | - 5900:5900
--------------------------------------------------------------------------------
/docker-compose.override.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | db:
4 | ports:
5 | - "9432:5432"
6 | frontend:
7 | image: node:5.10
8 | working_dir: /frontend
9 | command: npm run-script develop
10 | volumes:
11 | - ./frontend:/frontend
12 | ports:
13 | - "3000:3000"
14 | django:
15 | build: ./backend
16 | volumes:
17 | - ./backend:/app
18 | - ./backend/media:/media
19 | - ./frontend/dist:/static
20 | command: ./bin/develop.sh
21 | ports:
22 | - "8000:8000"
23 | environment:
24 | DJANGO_SETTINGS_MODULE: ${DJANGO_SETTINGS_DEV}
25 | depends_on:
26 | - db
--------------------------------------------------------------------------------
/frontend/webpack/dev-server.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import WebpackDevServer from 'webpack-dev-server';
3 | import config from './webpack.config.development';
4 |
5 | new WebpackDevServer(webpack(config), {
6 | publicPath: config.output.publicPath,
7 | filename: config.output.filename,
8 | inline: true,
9 | hot: true,
10 | stats: false,
11 | historyApiFallback: true,
12 | headers: {
13 | 'Access-Control-Allow-Origin': 'http://localhost:8000',
14 | 'Access-Control-Allow-Headers': 'X-Requested-With'
15 | }
16 | }).listen(3000, '0.0.0.0', function (err) {
17 | if (err) {
18 | console.error(err);
19 | } else {
20 | console.log('webpack dev server listening on localhost:3000');
21 | }
22 | });
23 |
--------------------------------------------------------------------------------
/backend/conf/settings_prod.py:
--------------------------------------------------------------------------------
1 | from .settings import *
2 |
3 |
4 | ALLOWED_HOSTS = ['*']
5 |
6 | DEBUG = False
7 | TEMPLATE_DEBUG = False
8 | WEBPACK_DEV_SERVER = False
9 |
10 | STATIC_ASSETS_JSON = os.path.join(BASE_DIR, 'assets.json')
11 |
12 | SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
13 |
14 | LOGGING = {
15 | 'version': 1,
16 | 'disable_existing_loggers': False,
17 | 'handlers': {
18 | 'applogfile': {
19 | 'level': 'DEBUG',
20 | 'class': 'logging.FileHandler',
21 | 'filename': '/tmp/logs/app/app.log',
22 | },
23 | },
24 | 'loggers': {
25 | 'django': {
26 | 'handlers': ['applogfile'],
27 | 'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
28 | },
29 | },
30 | }
--------------------------------------------------------------------------------
/frontend/webpack/webpack.config.development.js:
--------------------------------------------------------------------------------
1 | import base_config from './webpack.config.base';
2 | import webpack from 'webpack';
3 |
4 | export default {
5 | ...base_config,
6 | debug: true,
7 | output: {
8 | ...base_config.output,
9 | publicPath: 'http://localhost:3000' + base_config.output.publicPath,
10 | filename: 'bundle.js'
11 | },
12 | entry: [
13 | 'webpack-dev-server/client?http://0.0.0.0:3000',
14 | 'webpack/hot/dev-server',
15 | ...base_config.entry
16 | ],
17 | plugins: [
18 | ...base_config.plugins,
19 | new webpack.HotModuleReplacementPlugin(),
20 | new webpack.DefinePlugin({
21 | '__DEV__': true,
22 | 'process.env': JSON.stringify('development')
23 | })
24 | ]
25 | };
--------------------------------------------------------------------------------
/bin/env.sh:
--------------------------------------------------------------------------------
1 | export DOCKER_CONFIG_PROD=${DOCKER_CONFIG_PROD:-docker-compose.production.yml}
2 | export DOCKER_CONFIG_DEV=${DOCKER_CONFIG_DEV:-docker-compose.override.yml}
3 | export DOCKER_CONFIG_TEST=${DOCKER_CONFIG_TEST:-docker-compose.test.yml}
4 |
5 | export DB_USER=${DB_USER:-django}
6 | export DB_NAME=${DB_NAME:-django}
7 |
8 | export DJANGO_SETTINGS_DEV=${DJANGO_SETTINGS_DEV:-conf.settings}
9 | export DJANGO_SETTINGS_PROD=${DJANGO_SETTINGS_MODULE:-conf.settings_prod}
10 | export DJANGO_SETTINGS_TEST=${DJANGO_SETTINGS_TEST:-conf.settings_test}
11 |
12 | dcdev() {
13 | docker-compose -f docker-compose.yml -f $DOCKER_CONFIG_DEV "$@"
14 | }
15 |
16 | dcprod() {
17 | docker-compose -f docker-compose.yml -f $DOCKER_CONFIG_PROD "$@"
18 | }
19 |
20 | dctest() {
21 | docker-compose -f docker-compose.yml -f $DOCKER_CONFIG_TEST "$@"
22 | }
--------------------------------------------------------------------------------
/backend/urls.py:
--------------------------------------------------------------------------------
1 | """project URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/1.9/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.conf.urls import url, include
14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
15 | """
16 | from django.conf.urls import url, include
17 | from django.contrib import admin
18 |
19 | urlpatterns = [
20 | url(r'^admin/', admin.site.urls),
21 | url(r'^', include('apps.home.urls'))
22 | ]
23 |
--------------------------------------------------------------------------------
/docker-compose.production.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | db:
4 | volumes:
5 | - "./logs/postgres:/var/log/postgresql"
6 | django:
7 | build:
8 | context: ./backend
9 | dockerfile: Dockerfile-production
10 | environment:
11 | DJANGO_SETTINGS_MODULE: ${DJANGO_SETTINGS_PROD}
12 | volumes:
13 | - "./logs/app:/tmp/logs/app"
14 | - "./logs/gunicorn:/tmp/logs/gunicorn"
15 | - "./backend/media:/media"
16 | depends_on:
17 | - db
18 | nginx:
19 | build:
20 | context: ./
21 | dockerfile: ./nginx/Dockerfile
22 | depends_on:
23 | - django
24 | volumes:
25 | - "./backend/media:/media"
26 | - "./logs/nginx:/tmp/logs"
27 | ports:
28 | - "80:80"
29 | - "443:443"
30 |
31 |
--------------------------------------------------------------------------------
/bin/restore.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # restore a bacakup. arg is a filename that exitsts in backups dir
3 |
4 | source bin/env.sh
5 |
6 | BACKUP_FILE=$1
7 |
8 | if ! [ -f $BACKUP_FILE ]; then
9 | echo "file $BACKUP_FILE not found"
10 | exit 1
11 | fi
12 |
13 | if [ $(docker-compose -f docker-compose.yml -f $DOCKER_CONFIG_PROD -f docker-compose.db.yml ps | grep "_db_" | grep "Up" | wc -l) != 0 ]; then
14 | echo "database container running. please stop before trying to restore"
15 | exit 1
16 | fi
17 |
18 | echo "dropping database..."
19 | dcprod -f docker-compose.db.yml run --rm dbclient dropdb -h db -U $DB_USER $DB_NAME
20 | echo "creating database..."
21 | dcprod -f docker-compose.db.yml run --rm dbclient createdb -h db -U $DB_USER -O $DB_USER $DB_NAME
22 | echo "restoring database..."
23 | dcprod -f docker-compose.db.yml run --rm dbclient pg_restore -Fc -h db -U $DB_USER -d $DB_NAME $BACKUP_FILE
24 | echo "restore complete"
--------------------------------------------------------------------------------
/frontend/webpack/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 |
3 | export default {
4 | entry: [
5 | '/frontend/src/js/index.js'
6 | ],
7 | devtool: 'source-map',
8 | output: {
9 | path: '/frontend/dist/',
10 | publicPath: '/static/',
11 | },
12 | module: {
13 | loaders: [
14 | {
15 | test: /\.js$/,
16 | exclude: /node_modules/,
17 | loader: 'babel',
18 | },
19 | {
20 | test: /\.css$/,
21 | loader: 'style!css'
22 | },
23 | {
24 | test: /\.styl$/,
25 | loader: 'style!css!stylus'
26 | },
27 | {
28 | test: /\.(mp4|webm|mp3|ogg|wav|jpeg|jpg|bmp|ico|png|gif|ttf|otf|woff|eot)$/,
29 | loader: 'file?name=[path][name].[ext]?[hash]'
30 | }
31 | ]
32 | },
33 | target: 'web',
34 | plugins: [
35 | new webpack.NoErrorsPlugin()
36 | ]
37 | };
--------------------------------------------------------------------------------
/bin/init_db.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #initialize database if it hasn't been initialized yet
4 |
5 | DOCKER_INIT_DB_CONFIG=${DOCKER_INIT_DB_CONFIG:-$DOCKER_CONFIG_DEV}
6 |
7 | echo "inti db config: $DOCKER_INIT_DB_CONFIG"
8 |
9 | if [ $(docker-compose -f docker-compose.yml -f $DOCKER_INIT_DB_CONFIG ps | grep db | wc -l) == 0 ]; then
10 | echo "initializing database"
11 | docker-compose -f docker-compose.yml -f $DOCKER_INIT_DB_CONFIG up -d db
12 | DB_CONTAINER_ID=$(docker-compose -f docker-compose.yml -f $DOCKER_INIT_DB_CONFIG ps -q db)
13 | for i in {30..0}; do
14 | echo "waiting for postgres to finish initializing..."
15 | if [ $(docker logs $DB_CONTAINER_ID 2>&1 | grep "database system is ready to accept connections" | wc -l) == 1 ]; then
16 | docker-compose -f docker-compose.yml -f $DOCKER_INIT_DB_CONFIG stop db
17 | echo "db initialized"
18 | exit 0
19 | else
20 | sleep 1
21 | fi
22 | done
23 | if [ "$i" = 0 ]; then
24 | echo >&2 "db init failed"
25 | exit 1
26 | fi
27 | fi
28 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev-server": "babel-node webpack/dev-server.js",
8 | "npminstall": "npm i -q",
9 | "develop": "npm run npminstall && npm run dev-server",
10 | "build": "babel-node ./node_modules/webpack/bin/webpack --config webpack/webpack.config.production.js --progress --profile --color",
11 | "cleandist": "rm -rf /dist/*",
12 | "build-prod": "npm run npminstall && npm run cleandist && npm run build"
13 | },
14 | "author": "domasx2@gmail.com",
15 | "license": "ISC",
16 | "devDependencies": {
17 | "assets-webpack-plugin": "^3.4.0",
18 | "babel-cli": "^6.6.5",
19 | "babel-loader": "^6.2.4",
20 | "babel-plugin-transform-object-rest-spread": "^6.6.5",
21 | "babel-preset-es2015": "^6.6.0",
22 | "css-loader": "^0.23.1",
23 | "extract-text-webpack-plugin": "^1.0.1",
24 | "file-loader": "^0.8.5",
25 | "style-loader": "^0.13.1",
26 | "stylus": "^0.54.2",
27 | "stylus-loader": "^2.0.0",
28 | "webpack": "^1.12.14",
29 | "webpack-dev-server": "^1.14.1"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/frontend/webpack/webpack.config.production.js:
--------------------------------------------------------------------------------
1 | import base_config from './webpack.config.base';
2 | import webpack from 'webpack';
3 | import ExtractTextPlugin from 'extract-text-webpack-plugin';
4 | import AssetsWebpackPlugin from 'assets-webpack-plugin';
5 |
6 | export default {
7 | ...base_config,
8 |
9 | output: {
10 | ...base_config.output,
11 | filename: '[hash]_bundle.js'
12 | },
13 |
14 | module: {
15 | ...base_config.module,
16 |
17 | //wrap style loaders with extract text plugin
18 | loaders: base_config.module.loaders.map(function(conf) {
19 | return {
20 | ...conf,
21 | loader: conf.loader && conf.loader.includes('style!') ? ExtractTextPlugin.extract('style', conf.loader.replace('style!', '')) : conf.loader
22 | }
23 | })
24 | },
25 |
26 | plugins: [
27 | new webpack.optimize.OccurenceOrderPlugin(),
28 | new webpack.DefinePlugin({
29 | '__DEV__': false,
30 | 'process.env.NODE_ENV': JSON.stringify('production'),
31 | 'process.env': JSON.stringify('production')
32 | }),
33 | new webpack.optimize.UglifyJsPlugin({
34 | compressor: {
35 | screw_ie8: true,
36 | warnings: false
37 | }
38 | }),
39 | new ExtractTextPlugin("[hash]_styles.css"),
40 | new AssetsWebpackPlugin({filename: 'dist/assets.json'})
41 | ]
42 | };
--------------------------------------------------------------------------------
/nginx/nginx_nossl.conf:
--------------------------------------------------------------------------------
1 | worker_processes 1;
2 |
3 | user nobody nogroup;
4 | pid /tmp/nginx.pid;
5 | error_log /tmp/logs/nginx.error.log;
6 |
7 | events {
8 | worker_connections 1024;
9 | accept_mutex off;
10 | }
11 |
12 | http {
13 | include mime.types;
14 | default_type application/octet-stream;
15 | access_log /tmp/logs/nginx.access.log combined;
16 | sendfile on;
17 |
18 | server {
19 | listen 80 default;
20 | client_max_body_size 4G;
21 | server_name _;
22 |
23 |
24 | gzip on;
25 | gzip_disable "msie6";
26 | gzip_vary on;
27 | gzip_proxied any;
28 | gzip_comp_level 6;
29 | gzip_buffers 16 8k;
30 | gzip_http_version 1.1;
31 | gzip_types application/javascript text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
32 |
33 | keepalive_timeout 5;
34 |
35 | location /static/ {
36 | alias /static/;
37 | }
38 |
39 | location /media/ {
40 | alias /media/;
41 | }
42 |
43 | location / {
44 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
45 | proxy_set_header Host $http_host;
46 |
47 | # UNCOMMENT LINE BELOW IF THIS IS BEHIND A SSL PROXY
48 | #proxy_set_header X-Forwarded-Proto https;
49 |
50 | proxy_redirect off;
51 | proxy_pass http://django:8000;
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/nginx/nginx_ssl.conf:
--------------------------------------------------------------------------------
1 | # to use ssl, add your cert and key to conf/ssl,
2 | # replace cert/key names with real names below,
3 | # and replace nginx.conf with this
4 |
5 | worker_processes 1;
6 |
7 | user nobody nogroup;
8 | pid /tmp/nginx.pid;
9 | error_log /tmp/logs/nginx.error.log;
10 |
11 | events {
12 | worker_connections 1024;
13 | accept_mutex off;
14 | }
15 |
16 | http {
17 | include "/etc/nginx/mime.types";
18 | default_type application/octet-stream;
19 | sendfile on;
20 |
21 | server {
22 | listen 80 default;
23 | return 301 https://$host$request_uri;
24 | }
25 |
26 | server {
27 | listen 443;
28 | client_max_body_size 4G;
29 | server_name _;
30 |
31 | access_log /tmp/logs/nginx.access-ssl.log combined;
32 |
33 | ssl on;
34 |
35 | #rename with your key/cert here
36 | ssl_certificate /etc/nginx/ssl/server.crt;
37 | ssl_certificate_key /etc/nginx/ssl/server.key;
38 |
39 | ssl_prefer_server_ciphers On;
40 | ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
41 | ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS;
42 |
43 | gzip on;
44 | gzip_disable "msie6";
45 | gzip_vary on;
46 | gzip_proxied any;
47 | gzip_comp_level 6;
48 | gzip_buffers 16 8k;
49 | gzip_http_version 1.1;
50 | gzip_types application/javascript text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
51 |
52 | keepalive_timeout 5;
53 |
54 | location /static/ {
55 | alias /static/;
56 | }
57 |
58 | location /media/ {
59 | alias /media/;
60 | }
61 |
62 | location / {
63 | proxy_set_header Host $http_host;
64 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
65 | proxy_set_header X-Forwarded-Proto https;
66 | proxy_set_header X-Real-IP $remote_addr;
67 | proxy_set_header X-Scheme $scheme;
68 | proxy_redirect off;
69 | proxy_pass http://django:8000;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/backend/conf/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for project project.
3 |
4 | Generated by 'django-admin startproject' using Django 1.9.2.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.9/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/1.9/ref/settings/
11 | """
12 |
13 | import os
14 |
15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = '^h85uo#*&q+5r=a1_l8i_&t$^$(sgsz-3*ijq8%22ayrbapxyk'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | #load frontend from webpack dev server rather than static
29 | WEBPACK_DEV_SERVER = True
30 |
31 | ALLOWED_HOSTS = []
32 |
33 |
34 | # Application definition
35 |
36 | INSTALLED_APPS = [
37 | 'django.contrib.admin',
38 | 'django.contrib.auth',
39 | 'django.contrib.contenttypes',
40 | 'django.contrib.sessions',
41 | 'django.contrib.messages',
42 | 'django.contrib.staticfiles',
43 |
44 | 'apps.home'
45 | ]
46 |
47 | MIDDLEWARE_CLASSES = [
48 | 'django.middleware.security.SecurityMiddleware',
49 | 'django.contrib.sessions.middleware.SessionMiddleware',
50 | 'django.middleware.common.CommonMiddleware',
51 | 'django.middleware.csrf.CsrfViewMiddleware',
52 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
53 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
54 | 'django.contrib.messages.middleware.MessageMiddleware',
55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
56 | ]
57 |
58 | ROOT_URLCONF = 'urls'
59 |
60 | TEMPLATES = [
61 | {
62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
63 | 'DIRS': ['/app/templates/'],
64 | 'APP_DIRS': True,
65 | 'OPTIONS': {
66 | 'context_processors': [
67 | 'django.template.context_processors.debug',
68 | 'django.template.context_processors.request',
69 | 'django.contrib.auth.context_processors.auth',
70 | 'django.contrib.messages.context_processors.messages',
71 | 'context_processors.static_resources'
72 | ],
73 | },
74 | },
75 | ]
76 |
77 | WSGI_APPLICATION = 'wsgi.application'
78 |
79 |
80 | # Database
81 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases
82 |
83 | DATABASES = {
84 | 'default': {
85 | 'ENGINE': 'django.db.backends.postgresql_psycopg2',
86 | 'NAME': 'django',
87 | 'USER': 'django',
88 | 'HOST': 'db',
89 | 'PORT': 5432,
90 | }
91 | }
92 |
93 |
94 |
95 | # Password validation
96 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
97 |
98 | AUTH_PASSWORD_VALIDATORS = [
99 | {
100 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
101 | },
102 | {
103 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
104 | },
105 | {
106 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
107 | },
108 | {
109 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
110 | },
111 | ]
112 |
113 |
114 | # Internationalization
115 | # https://docs.djangoproject.com/en/1.9/topics/i18n/
116 |
117 | LANGUAGE_CODE = 'en-us'
118 |
119 | TIME_ZONE = 'UTC'
120 |
121 | USE_I18N = True
122 |
123 | USE_L10N = True
124 |
125 | USE_TZ = True
126 |
127 |
128 | # Static files (CSS, JavaScript, Images)
129 | # https://docs.djangoproject.com/en/1.9/howto/static-files/
130 |
131 | STATIC_URL = '/static/'
132 | STATIC_ROOT = '/static/'
133 |
134 | STATIC_ASSETS_JSON = os.path.join(STATIC_ROOT, 'assets.json')
135 |
136 | # user uploads
137 | MEDIA_URL = '/media/'
138 | MEDIA_ROOT = '/media/'
139 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # docker-django-webpack-starter
2 |
3 | This is a starter project for a django app with webpack built frontend that uses docker for dev enironment.
4 | Docker and docker-compose is all you need to develop, build & deploy, run development or production mode with a single command.
5 |
6 | ## stack
7 | python 3.5
8 | node 5.10
9 | Postgres 9.5
10 | Django 1.10.5
11 | Webpack
12 | Stylus
13 | Nginx
14 | Gunicorn
15 |
16 |
17 | ## get started
18 |
19 | Get latest docker & docker-compose:
20 | https://www.docker.com/
21 | https://docs.docker.com/compose/
22 |
23 | Pull seed to your project:
24 | ```sh
25 | git init
26 | git remote add starter https://github.com/domasx2/docker-django-webpack-starter.git
27 | git pull starter master
28 | ```
29 |
30 | Start dev server:
31 | ```sh
32 | ./bin/develop.sh
33 | ```
34 | Wait for docker to set up container, then open [http://localhost:8000](http://localhost:8000)
35 |
36 | ## production mode
37 |
38 | ```sh
39 | # build production images, create db backup & start
40 | ./bin/deploy.sh
41 |
42 | # stop server
43 | ./bin/stop_production.sh
44 |
45 | # start srever
46 | ./bin/start_production.sh
47 | ```
48 |
49 | In prod mode sources are added to docker image rather than mounted from host. Nginx serves static files, proxy pass to gunicorn for django app. Logs in `logs` dir.
50 |
51 | #### enable ssl
52 | Copy your .key and .crt files to `nginx/ssl` and run `./bin/deploy.sh`.
53 |
54 | ## install dependencies
55 | ```sh
56 | # frontend
57 | ./bin/npm.sh install [package] --save-dev
58 |
59 | # backend
60 | ./bin/pipinstall.sh [pacakge] #will also add entry to backend/requirements.txt
61 | ```
62 |
63 | ## backup & restore database
64 |
65 | ```sh
66 | # create a backup in backups dir
67 | ./bin/backup.sh
68 |
69 | # restore from a backup in backups dir (server must be stopped)
70 | ./bin/restore.sh backups/somebackup.bak
71 | ```
72 |
73 | ## run django management commands
74 | ```sh
75 | #dev mode
76 | ./bin/django.sh [command]
77 |
78 | #create migration
79 | ./bin/django.sh makemigrations myapp
80 |
81 | #prod mode
82 | ./bin/django_prod.sh [command]
83 |
84 | #start django shell in prod mode
85 | ./bin/django_prod.sh shell
86 |
87 | ```
88 |
89 | ## layout
90 |
91 | ```
92 | bin/ - various utility scripts
93 |
94 | docker-compose.yml - base docker compose config
95 | docker-compose.overrides.yml - development docker compose config
96 | docker-compose.production.yml - production docker compose config
97 |
98 | frontend/ - frontend stuff
99 | frontend/package.json - npm package file with frotnend dependencies
100 | frontend/src/js/ - javascript code
101 | frontend/src/js/index.js - js entry point. include other js deps here
102 | frontend/src/style/ - stylesheets
103 | frontend/src/style/index.styl - stylesheet entry point. include other styl files here
104 |
105 | backend/ - backend stuff
106 | backend/apps/ - django apps
107 | backend/conf/ - django settings files
108 | backend/conf/settings.py - default config
109 | backend/conf/settings_prod.py - production config
110 | backend/templates/ - django global templates
111 | backend/requirements.txt - python dependencies
112 | backend/gunicorn.conf.py - gunicorn conf for production
113 | backend/media/ - user uploads
114 |
115 | logs/ - in prod mode app, gunicorn, nginx, postgres logs go here
116 | nginx/ - nginx stuff for prod mode
117 | nginx/ssl/ - put key & cert here if you use ssl
118 | nginx/nginx_nossl.conf - nginx conf if no ssl is used
119 | nginx/nginx_ssl.conf - nginx conf for deploy with ssl
120 | ```
121 |
122 |
123 | ## tests
124 |
125 | For e2e tests, use `app.testutils.SeleniumTestCase` class it comes with chrome driver configure at `self.driver`. See `apps.home.tests` for an example.
126 | See http://selenium-python.readthedocs.io/ for selenium driver api
127 |
128 | ```sh
129 |
130 | #run tests
131 | ./bin/test.sh
132 |
133 | # skip frontend build (eg, running tests repeatedly)
134 | ./bin/test.sh --skipbuild
135 |
136 |
137 | To debug tests it's possible to vnc into selenium container while its running at localhost:5900 and view the browser. Password is `secret`.
138 |
139 | ```sh
140 | sudo apt-get install vinagre # vnc client
141 |
142 | vinagre localhost:5900
143 | ```
--------------------------------------------------------------------------------