├── src ├── cfehome │ ├── __init__.py │ ├── asgi.py │ ├── wsgi.py │ ├── urls.py │ └── settings.py ├── requirements.txt ├── config │ └── entrypoint.sh └── manage.py ├── Makefile ├── Dockerfile ├── nginx ├── index.html └── default.conf ├── README.md └── .gitignore /src/cfehome/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | django 2 | gunicorn -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | docker build -t django-nginx -f Dockerfile . 3 | 4 | run: 5 | docker run -p 8000:80 -e PORT=8000 --name django-nginx --rm django-nginx 6 | 7 | stop: 8 | docker stop django-nginx 9 | 10 | logs: 11 | docker logs -f django-nginx -------------------------------------------------------------------------------- /src/config/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | RUN_PORT="8000" 3 | 4 | /opt/venv/bin/python manage.py migrate --noinput 5 | /opt/venv/bin/gunicorn cfehome.wsgi:application --bind "0.0.0.0:${RUN_PORT}" --daemon --access-logfile /dev/stdout --capture-output --log-level info 6 | 7 | nginx -g 'daemon off;error_log /dev/stdout info;' -------------------------------------------------------------------------------- /src/cfehome/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for cfehome project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cfehome.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /src/cfehome/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for cfehome 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/4.1/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", "cfehome.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim 2 | 3 | RUN apt-get update && apt-get install nginx -y 4 | 5 | COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf 6 | COPY ./nginx/index.html /var/www/html/index.html 7 | RUN ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log 8 | 9 | COPY ./src ./app 10 | WORKDIR /app 11 | 12 | RUN python -m venv /opt/venv && \ 13 | /opt/venv/bin/python -m pip install pip --upgrade && \ 14 | /opt/venv/bin/python -m pip install -r requirements.txt 15 | 16 | RUN chmod +x config/entrypoint.sh 17 | 18 | CMD ["./config/entrypoint.sh"] -------------------------------------------------------------------------------- /nginx/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | django-nginx 6 | 13 | 14 | 15 |

Welcome to django-nginx!

16 |

If you see this page, the django-nginx web server is successfully installed and 17 | working. Further configuration is required.

18 | 19 |

For online documentation and community discussion please refer to 20 | the django-nginx repo.
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/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", "cfehome.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 | -------------------------------------------------------------------------------- /nginx/default.conf: -------------------------------------------------------------------------------- 1 | upstream django_project { 2 | server localhost:8000; 3 | } 4 | 5 | error_log /var/log/nginx/error.log; 6 | 7 | server { 8 | listen 80; 9 | server_name codingforentrepreneurs.com *.codingforentrepreneurs.com; 10 | root /www/data/; 11 | access_log /var/log/nginx/access.log; 12 | 13 | 14 | location / { 15 | proxy_pass http://django_project; 16 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 17 | proxy_set_header Host $host; 18 | proxy_redirect off; 19 | } 20 | 21 | location /static { 22 | autoindex on; 23 | alias /www/static/; 24 | } 25 | 26 | error_page 500 502 503 504 /50x.html; 27 | location = /50x.html { 28 | root /usr/share/nginx/html; 29 | } 30 | } -------------------------------------------------------------------------------- /src/cfehome/urls.py: -------------------------------------------------------------------------------- 1 | """cfehome URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/4.1/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: path('', 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: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path 18 | 19 | urlpatterns = [ 20 | path("admin/", admin.site.urls), 21 | ] 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django, Gunicorn, & NGINX Running together within a Container 2 | If you're running [Django](https://djangoproject.com) & [gunicorn](https://docs.gunicorn.org/) within a [Docker](https://www.docker.com) container and need to limit access to a specific domain, what do you do? 3 | 4 | The answer? Run NGINX along side gunicorn and Django. 5 | 6 | The [related post](https://www.codingforentrepreneurs.com/blog/django-gunicorn-nginx-docker) will describe exactly how you do this. Let's not confuse this with running two containers but rather running nginx and gunicorn with the same container. 7 | 8 | To do this, we'll have Django running via gunicorn as a daemon process (i.e. background) and NGINX as a non-daemon process (i.e. non-background). 9 | 10 | The contents in this repo are _not production-ready_ but can be modified to be production-ready by at least using environment variables, an actual production database, turning off debug mode in Django, leveraging a static files server, and likely other items that you can learn about in my course [Django Deployment Pipeline](https://www.codingforentrepreneurs.com/courses/django-deployment-pipeline/). 11 | 12 | 13 | ## [Reference Blog Post](https://www.codingforentrepreneurs.com/blog/django-gunicorn-nginx-docker) -------------------------------------------------------------------------------- /src/cfehome/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for cfehome project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.1.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.1/ref/settings/ 11 | """ 12 | import os 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | # Create a one-off secret key: https://cfe.sh/blog/create-a-one-off-django-secret-key/ 24 | SECRET_KEY = os.environ.get("SECRET_KEY") 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = False 28 | 29 | ALLOWED_HOSTS = ['.codingforentrepreneurs.com'] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = [ 35 | "django.contrib.admin", 36 | "django.contrib.auth", 37 | "django.contrib.contenttypes", 38 | "django.contrib.sessions", 39 | "django.contrib.messages", 40 | "django.contrib.staticfiles", 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | "django.middleware.security.SecurityMiddleware", 45 | "django.contrib.sessions.middleware.SessionMiddleware", 46 | "django.middleware.common.CommonMiddleware", 47 | "django.middleware.csrf.CsrfViewMiddleware", 48 | "django.contrib.auth.middleware.AuthenticationMiddleware", 49 | "django.contrib.messages.middleware.MessageMiddleware", 50 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 51 | ] 52 | 53 | ROOT_URLCONF = "cfehome.urls" 54 | 55 | TEMPLATES = [ 56 | { 57 | "BACKEND": "django.template.backends.django.DjangoTemplates", 58 | "DIRS": [], 59 | "APP_DIRS": True, 60 | "OPTIONS": { 61 | "context_processors": [ 62 | "django.template.context_processors.debug", 63 | "django.template.context_processors.request", 64 | "django.contrib.auth.context_processors.auth", 65 | "django.contrib.messages.context_processors.messages", 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = "cfehome.wsgi.application" 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/4.1/ref/settings/#databases 76 | 77 | DATABASES = { 78 | "default": { 79 | "ENGINE": "django.db.backends.sqlite3", 80 | "NAME": BASE_DIR / "db.sqlite3", 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 91 | }, 92 | { 93 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 94 | }, 95 | { 96 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 97 | }, 98 | { 99 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/4.1/topics/i18n/ 106 | 107 | LANGUAGE_CODE = "en-us" 108 | 109 | TIME_ZONE = "UTC" 110 | 111 | USE_I18N = True 112 | 113 | USE_TZ = True 114 | 115 | 116 | # Static files (CSS, JavaScript, Images) 117 | # https://docs.djangoproject.com/en/4.1/howto/static-files/ 118 | 119 | STATIC_URL = "static/" 120 | 121 | # Default primary key field type 122 | # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field 123 | 124 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 125 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.code-workspace 2 | ARTICLE.md 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # poetry 101 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 102 | # This is especially recommended for binary packages to ensure reproducibility, and is more 103 | # commonly ignored for libraries. 104 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 105 | #poetry.lock 106 | 107 | # pdm 108 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 109 | #pdm.lock 110 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 111 | # in version control. 112 | # https://pdm.fming.dev/#use-with-ide 113 | .pdm.toml 114 | 115 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 116 | __pypackages__/ 117 | 118 | # Celery stuff 119 | celerybeat-schedule 120 | celerybeat.pid 121 | 122 | # SageMath parsed files 123 | *.sage.py 124 | 125 | # Environments 126 | .env 127 | .venv 128 | env/ 129 | venv/ 130 | ENV/ 131 | env.bak/ 132 | venv.bak/ 133 | 134 | # Spyder project settings 135 | .spyderproject 136 | .spyproject 137 | 138 | # Rope project settings 139 | .ropeproject 140 | 141 | # mkdocs documentation 142 | /site 143 | 144 | # mypy 145 | .mypy_cache/ 146 | .dmypy.json 147 | dmypy.json 148 | 149 | # Pyre type checker 150 | .pyre/ 151 | 152 | # pytype static type analyzer 153 | .pytype/ 154 | 155 | # Cython debug symbols 156 | cython_debug/ 157 | 158 | # PyCharm 159 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 160 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 161 | # and can be added to the global gitignore or merged into this file. For a more nuclear 162 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 163 | #.idea/ --------------------------------------------------------------------------------