├── 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 |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/
--------------------------------------------------------------------------------