├── .env.sample ├── .gitignore ├── Dockerfile ├── README.md ├── app ├── .gitkeep ├── app │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── home │ ├── __init__.py │ ├── apps.py │ ├── templates │ │ └── home │ │ │ └── index.html │ └── views.py └── manage.py ├── docker-compose.deploy.yml ├── docker-compose.yml ├── docker ├── certbot │ ├── Dockerfile │ └── certify-init.sh └── proxy │ ├── Dockerfile │ ├── nginx │ ├── default-ssl.conf.tpl │ ├── default.conf.tpl │ ├── options-ssl-nginx.conf │ └── uwsgi_params │ └── run.sh └── requirements.txt /.env.sample: -------------------------------------------------------------------------------- 1 | DJANGO_SECRET_KEY=secretkey123 2 | ACME_DEFAULT_EMAIL=email@example.com 3 | DOMAIN=example.com 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-alpine3.16 2 | 3 | ENV PYTHONUNBUFFERED 1 4 | 5 | COPY requirements.txt /requirements.txt 6 | RUN apk add --upgrade --no-cache build-base linux-headers && \ 7 | pip install --upgrade pip && \ 8 | pip install -r /requirements.txt 9 | 10 | COPY app/ /app 11 | WORKDIR /app 12 | 13 | RUN adduser --disabled-password --no-create-home django 14 | 15 | USER django 16 | 17 | CMD ["uwsgi", "--socket", ":9000", "--workers", "4", "--master", "--enable-threads", "--module", "app.wsgi"] 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
Full-Stack Consulting and Courses.
9 | Website | 10 | Courses | 11 | Tutorials | 12 | Consulting 13 |This is a Django app with HTTPS enabled!
8 | 9 | 10 | -------------------------------------------------------------------------------- /app/home/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | def index(request): 4 | return render(request, "home/index.html") 5 | -------------------------------------------------------------------------------- /app/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /docker-compose.deploy.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | restart: always 8 | environment: 9 | - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY} 10 | - DJANGO_ALLOWED_HOSTS=${DOMAIN} 11 | 12 | proxy: 13 | build: 14 | context: ./docker/proxy 15 | restart: always 16 | depends_on: 17 | - app 18 | ports: 19 | - 80:80 20 | - 443:443 21 | volumes: 22 | - certbot-web:/vol/www 23 | - proxy-dhparams:/vol/proxy 24 | - certbot-certs:/etc/letsencrypt 25 | environment: 26 | - DOMAIN=${DOMAIN} 27 | 28 | certbot: 29 | build: 30 | context: ./docker/certbot 31 | command: echo "Skipping..." 32 | environment: 33 | - EMAIL=${ACME_DEFAULT_EMAIL} 34 | - DOMAIN=${DOMAIN} 35 | volumes: 36 | - certbot-web:/vol/www 37 | - certbot-certs:/etc/letsencrypt/ 38 | depends_on: 39 | - proxy 40 | 41 | volumes: 42 | certbot-web: 43 | proxy-dhparams: 44 | certbot-certs: 45 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | command: sh -c "python manage.py runserver 0.0.0.0:8000" 8 | volumes: 9 | - ./app:/app 10 | ports: 11 | - 8000:8000 12 | environment: 13 | - DJANGO_DEBUG=1 14 | -------------------------------------------------------------------------------- /docker/certbot/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM certbot/certbot:v1.27.0 2 | 3 | COPY certify-init.sh /opt/ 4 | RUN chmod +x /opt/certify-init.sh 5 | 6 | ENTRYPOINT [] 7 | CMD ["certbot", "renew"] 8 | -------------------------------------------------------------------------------- /docker/certbot/certify-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Waits for proxy to be available, then gets the first certificate. 4 | 5 | set -e 6 | 7 | until nc -z proxy 80; do 8 | echo "Waiting for proxy..." 9 | sleep 5s & wait ${!} 10 | done 11 | 12 | echo "Getting certificate..." 13 | 14 | certbot certonly \ 15 | --webroot \ 16 | --webroot-path "/vol/www/" \ 17 | -d "$DOMAIN" \ 18 | --email $EMAIL \ 19 | --rsa-key-size 4096 \ 20 | --agree-tos \ 21 | --noninteractive 22 | -------------------------------------------------------------------------------- /docker/proxy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.23.0-alpine 2 | 3 | COPY ./nginx/* /etc/nginx/ 4 | COPY ./run.sh /run.sh 5 | 6 | ENV APP_HOST=app 7 | ENV APP_PORT=9000 8 | 9 | USER root 10 | 11 | RUN apk add --no-cache openssl bash 12 | RUN chmod +x /run.sh 13 | 14 | VOLUME /vol/static 15 | VOLUME /vol/www 16 | 17 | CMD ["/run.sh"] 18 | -------------------------------------------------------------------------------- /docker/proxy/nginx/default-ssl.conf.tpl: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name ${DOMAIN} www.${DOMAIN}; 4 | 5 | location /.well-known/acme-challenge/ { 6 | root /vol/www/; 7 | } 8 | 9 | location / { 10 | return 301 https://$host$request_uri; 11 | } 12 | } 13 | 14 | server { 15 | listen 443 ssl; 16 | server_name ${DOMAIN} www.${DOMAIN}; 17 | 18 | ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem; 19 | ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem; 20 | 21 | include /etc/nginx/options-ssl-nginx.conf; 22 | 23 | ssl_dhparam /vol/proxy/ssl-dhparams.pem; 24 | 25 | add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; 26 | 27 | location /static { 28 | alias /vol/static; 29 | } 30 | 31 | location / { 32 | uwsgi_pass ${APP_HOST}:${APP_PORT}; 33 | include /etc/nginx/uwsgi_params; 34 | client_max_body_size 10M; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docker/proxy/nginx/default.conf.tpl: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name ${DOMAIN} www.${DOMAIN}; 4 | 5 | location /.well-known/acme-challenge/ { 6 | root /vol/www/; 7 | } 8 | 9 | location / { 10 | return 301 https://$host$request_uri; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docker/proxy/nginx/options-ssl-nginx.conf: -------------------------------------------------------------------------------- 1 | # Taken from: 2 | # https://github.com/certbot/certbot/blob/1.28.0/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf 3 | 4 | ssl_session_cache shared:le_nginx_SSL:10m; 5 | ssl_session_timeout 1440m; 6 | ssl_session_tickets off; 7 | 8 | ssl_protocols TLSv1.2 TLSv1.3; 9 | ssl_prefer_server_ciphers off; 10 | 11 | ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; 12 | -------------------------------------------------------------------------------- /docker/proxy/nginx/uwsgi_params: -------------------------------------------------------------------------------- 1 | uwsgi_param QUERY_STRING $query_string; 2 | uwsgi_param REQUEST_METHOD $request_method; 3 | uwsgi_param CONTENT_TYPE $content_type; 4 | uwsgi_param CONTENT_LENGTH $content_length; 5 | uwsgi_param REQUEST_URI $request_uri; 6 | uwsgi_param PATH_INFO $document_uri; 7 | uwsgi_param DOCUMENT_ROOT $document_root; 8 | uwsgi_param SERVER_PROTOCOL $server_protocol; 9 | uwsgi_param REMOTE_ADDR $remote_addr; 10 | uwsgi_param REMOTE_PORT $remote_port; 11 | uwsgi_param SERVER_ADDR $server_addr; 12 | uwsgi_param SERVER_PORT $server_port; 13 | uwsgi_param SERVER_NAME $server_name; 14 | -------------------------------------------------------------------------------- /docker/proxy/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "Checking for dhparams.pem" 6 | if [ ! -f "/vol/proxy/ssl-dhparams.pem" ]; then 7 | echo "dhparams.pem does not exist - creating it" 8 | openssl dhparam -out /vol/proxy/ssl-dhparams.pem 2048 9 | fi 10 | 11 | echo "Checking for fullchain.pem" 12 | if [ ! -f "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" ]; then 13 | echo "No SSL cert, enabling HTTP only..." 14 | envsubst < /etc/nginx/default.conf.tpl > /etc/nginx/conf.d/default.conf 15 | else 16 | echo "SSL cert exists, enabling HTTPS..." 17 | envsubst < /etc/nginx/default-ssl.conf.tpl > /etc/nginx/conf.d/default.conf 18 | fi 19 | 20 | nginx-debug -g 'daemon off;' 21 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==4.0.5 2 | uWSGI==2.0.20 3 | --------------------------------------------------------------------------------