@@ -41,14 +41,25 @@ so that people can access it using a regular URL.
41
41
42
42
Perhaps more importantly, we shouldn't use the Django dev server for production;
43
43
it's not designed for real-life workloads.
44
- Instead, we'll use the popular Gunicorn Python/WSGI server.
44
+ Instead, we'll use the popular Gunicorn Python WSGI HTTP server.
45
+
46
+ NOTE: Django's `runserver` is built and optimised for local development and debugging.
47
+ It's designed to handle one user at a time,
48
+ it handles automatic reloading upon saving of the source code,
49
+ but it isn't optimised for performance,
50
+ nor has it been hardened against security vulnerabilities.
45
51
46
52
((("DEBUG settings")))
47
53
In addition, several options in _settings.py_ are currently unacceptable.
48
- `DEBUG=True`, is strongly recommended against for production,
54
+ `DEBUG=True`, is strongly discouraged for production,
49
55
we'll want to set a unique `SECRET_KEY`,
50
56
and, as we'll see, other things will come up.
51
57
58
+ NOTE: DEBUG=True is considered a security risk,
59
+ because the django debug page will display sensitive information like
60
+ the values of variables, and most of the settings in settings.py.
61
+
62
+
52
63
Let's go through and see if we can fix things one by one.
53
64
54
65
// SEBASTIAN: How about linking to Django's documentation
@@ -68,6 +79,47 @@ which I guess is what you'd want next if you already had a pony...
68
79
We'll need to first install Gunicorn into our container,
69
80
and then use it instead of `runserver`:
70
81
82
+ //001
83
+
84
+ [subs="specialcharacters,quotes"]
85
+ ----
86
+ $ *pip install gunicorn*
87
+ Collecting gunicorn
88
+ [...]
89
+ Successfully installed gunicorn-21.2.0
90
+ ----
91
+
92
+ [[what-is-wsgi]]
93
+ .What is WSGI?
94
+ *******************************************************************************
95
+ Gunicorn is a WSGI (usually pronounced as whizgee) HTTP server.
96
+ Let's go through what this means:
97
+
98
+ * An HTTP server handles incoming HTTP requests and sends back responses to the
99
+ clients.
100
+
101
+ * WSGI, the Web Server Gateway Interface, is a set of rules defining how an HTTP
102
+ server (which can be Gunicorn itself, or Nginx, Apache, etc) and a Python
103
+ program should communicate.
104
+
105
+ So, Gunicorn is an HTTP server that knows and uses the WSGI to "talk" to Python programs.
106
+ And these Python programs are likewise expected to work in a specific way.
107
+ You can check out a simple example https://gunicorn.org/[on Gunicorn's website] under "Installation".
108
+
109
+ https://peps.python.org/pep-3333[If you are interested, you can read the entire
110
+ specification here].
111
+
112
+ // CSANAD: I felt like some explanation regarding what Gunicorn and WSGI are,
113
+ // was missing.
114
+
115
+ *******************************************************************************
116
+
117
+
118
+ Gunicorn will need to know a path to a "WSGI server" (see <<what-is-wsgi>>),
119
+ which is usually a function called `application`.
120
+ Django provides one in 'superlists/wsgi.py'.
121
+ Let's change the command our image runs:
122
+
71
123
[role="sourcecode"]
72
124
.Dockerfile (ch10l001)
73
125
====
@@ -83,6 +135,9 @@ WORKDIR /src
83
135
CMD gunicorn --bind :8888 superlists.wsgi:application <2>
84
136
----
85
137
====
138
+ // CSANAD: shouldn't we try it out before adding this to the Dockerfile?
139
+ // `cd src` and then
140
+ // `gunicorn --bind :8888 superlists.wsgi:application`
86
141
87
142
<1> Installation is a standard pip install.
88
143
@@ -172,6 +227,11 @@ MIDDLEWARE = [
172
227
173
228
----
174
229
====
230
+ // CSANAD: I would add a few thoughts on the significance of the order of
231
+ // middlewares.
232
+
233
+ And if you take another manual look at your site after rebuilding the
234
+ container, things should look much healthier.
175
235
176
236
And then we need to add it to our pip installs in the Dockerfile:
177
237
@@ -290,7 +350,7 @@ Django, Gunicorn and Whitenoise:
290
350
[subs="specialcharacters,quotes"]
291
351
----
292
352
$ *pip freeze | grep -i django*
293
- Django==4.2.7
353
+ Django==4.2.11
294
354
295
355
$ *pip freeze | grep -i django >> requirements.txt*
296
356
$ *pip freeze | grep -i gunicorn >> requirements.txt*
@@ -351,7 +411,7 @@ TIP: Itamar Turner-Traurig has a great guide to
351
411
352
412
Now let's see how we use that requirements file in our Dockerfile:
353
413
354
- .Dockerfile (ch09l005 )
414
+ .Dockerfile (ch10l005 )
355
415
====
356
416
[source,dockerfile]
357
417
----
@@ -371,9 +431,9 @@ CMD python manage.py runserver
371
431
----
372
432
====
373
433
374
- <3 > We COPY our requirements file in, just like the src folder.
434
+ <1 > We COPY our requirements file in, just like the src folder.
375
435
376
- <4 > Now instead of just installing Django, we install all our dependencies
436
+ <2 > Now instead of just installing Django, we install all our dependencies
377
437
by pointing pip at the _requirements.txt_ using the `-r` flag.
378
438
Notice the `-r`.
379
439
@@ -437,7 +497,8 @@ Environment variables also have the advantage of working for non-Django stuff to
437
497
==== Setting DEBUG=True and SECRET_KEY
438
498
439
499
//RITA: What do you mean by "this"? Please add a word or two for context.
440
- There's lots of ways you might do this.
500
+ There are lots of ways you might do this.
501
+
441
502
Here's what I propose; it may seem a little fiddly,
442
503
but I'll provide a little justification for each choice.
443
504
Let them be an inspiration (but not a template) for your own choices!
@@ -459,6 +520,10 @@ else:
459
520
SECRET_KEY = "insecure-key-for-dev"
460
521
----
461
522
====
523
+ // CSANAD: I think variable names like "something_false" are confusing, since
524
+ // we need to set something to true so that they mean false.
525
+ // How about `DJANGO_ENV_PRODUCTION` or something similar?
526
+ // TODO yes i like this
462
527
463
528
<1> We say we'll use an environment variable called `DJANGO_DEBUG_FALSE`
464
529
to switch debug mode off, and in effect require production settings
@@ -479,6 +544,14 @@ against accidentally forgetting to set one.
479
544
TIP: Better to fail hard than allow a typo in an environment variable name to
480
545
leave you running with insecure settings.
481
546
547
+ // CSANAD: I think it would worth pointing out the development environment
548
+ // does not use Docker, launching the dev server should be done from
549
+ // the reader's host system. I think this isn't immediately obvious, e.g. I
550
+ // thought all along that from now on we would only run the server from Docker.
551
+ // If we end up making a TIP or similar about it, I think we should also mention
552
+ // in a development environment relying on containerization, programmers usually
553
+ // mount the whole /src minimizing the time-consuming rebuilding of their images.
554
+
482
555
==== Setting environment variables inside the Dockerfile
483
556
484
557
Now let's set that environment variable in our Dockerfile using the `ENV` directive:
@@ -555,6 +628,9 @@ It's not quite working yet! Let's take a look manually: <<django-400-error>>.
555
628
.An ugly 400 error
556
629
image::images/twp2_1002.png["An unfriendly page showing 400 Bad Request"]
557
630
631
+ // DAVID: noticed in passing that the screenshots in this chapter are from the hosted version
632
+ // which isn't covered until next chapter.
633
+
558
634
We've set our two environment variables but doing so seems to have broken things.
559
635
But once again, by running our FTs frequently,
560
636
we're able to identify the problem early,
@@ -627,6 +703,10 @@ $ *docker build -t superlists . && docker run \
627
703
An FT run (or just looking at the site) reveals that we've had a regression
628
704
in our static files:
629
705
706
+ // CSANAD: worth mentioning there was another clue. in the logs, once Gunicorn
707
+ // starts running you can see:
708
+ // `UserWarning: No directory at: /src/static/`
709
+
630
710
[role="small-code"]
631
711
[subs="specialcharacters,macros"]
632
712
----
@@ -658,6 +738,7 @@ ENV DJANGO_DEBUG_FALSE=1
658
738
CMD gunicorn --bind :8888 superlists.wsgi:application
659
739
----
660
740
====
741
+ // CSANAD: I think it would be important to use a non-privileged user in Docker
661
742
662
743
// DAVID: It would be nice to explain the difference between RUN and CMD.
663
744
@@ -698,6 +779,12 @@ $ *git status*
698
779
$ *git commit -am "Add collectstatic to dockerfile, and new location to gitignore"*
699
780
----
700
781
782
+ // CSANAD: now would be a good time to check our changes and notice git marked
783
+ // src/static as unstaged, so we should update .gitignore accordingly:
784
+ // from `static` to `src/static`.
785
+ // Or we could add this step to Chapter 09 at "Move all our code into a src
786
+ // folder"
787
+
701
788
702
789
=== Switching to a nonroot user
703
790
@@ -715,7 +802,6 @@ USER nonroot
715
802
----
716
803
717
804
718
-
719
805
=== Configuring logging
720
806
721
807
One last thing we'll want to do is make sure that we can get logs out of our server.
@@ -777,7 +863,7 @@ but it's more of shock to see that they are no longer appearing in the terminal
777
863
If you're like me you might find yourself wondering if we really _did_ see them earlier
778
864
and starting to doubt your own sanity.
779
865
But the explanation is that Django's
780
- https://docs.djangoproject.com/en/5.0 /ref/logging/#default-logging-configuration[default logging configuration]
866
+ https://docs.djangoproject.com/en/4.2 /ref/logging/#default-logging-configuration[default logging configuration]
781
867
changes when DEBUG is turned off:
782
868
783
869
This means we need to interact with the standard library's `logging` module,
@@ -790,7 +876,7 @@ but sometimes you just wanna say "just print stuff to stdout pls",
790
876
and you wish that configuring the simplest thing was a little easier.].
791
877
792
878
Here's pretty much the simplest possible logging config
793
- which just prints everything to the console (ie standard out).
879
+ which just prints everything to the console (i.e. standard out).
794
880
795
881
796
882
[role="sourcecode"]
@@ -832,7 +918,7 @@ django.db.utils.OperationalError: no such table: lists_list
832
918
833
919
Re-create the database with `./src/manage.py migrate` and we'll be back to a working state.
834
920
835
- Don't forget to commit our changes to _settings.py_,
921
+ Don't forget to commit our changes to _settings.py_ and _Dockerfile_ ,
836
922
and I think we can call it job done!
837
923
We've at least touched on many or most of the things you might need to think about
838
924
when considering production-readiness,
0 commit comments