Skip to content

Commit c928ad9

Browse files
committed
Dockerfile: Various bug fixes, clean ups, and an integration test
TLDR: * Fix all the headers for correctness (mime + charset), performance, and security. * Use much simpler redirect logic and apply the same approach as we use today. * Verify it all with an integration test. == Differences in file headers In summary: * Missing GZIP support and no "Vary: Accept-Encoding" to avoid cache poisoning. * Missing CORS support (Access-Control-Allow-Origin). * Missing caching allowance (Cache-Control public 10 years, max Expires). * Missing disablement of server_tokens. * Missing charset utf-8. * Unstable E-Tag and Last-Modified which would cause serious cache churn after every rebuild. Status quo: ``` krinkle@wp-03:~$ curl -H 'Host: codeorigin.jquery.com' -i 'http://localhost/jquery-3.0.0.js' | head -n20 Server: nginx Date: Mon, 16 Aug 2021 23:12:52 GMT Connection: keep-alive Access-Control-Allow-Origin: * Cache-Control: max-age=315360000 Cache-Control: public Content-Length: 263268 Content-Type: application/javascript; charset=utf-8 ETag: "28feccc0-40464" Expires: Thu, 31 Dec 2037 23:55:55 GMT Last-Modified: Fri, 18 Oct 1991 12:00:00 GMT Vary: Accept-Encoding Accept-Ranges: bytes ``` Previously, without this patch: ``` dockerize$ curl -i 'http://localhost:4000/jquery-3.0.0.js' | head -n20 Server: nginx/1.21.1 Date: Mon, 16 Aug 2021 23:13:30 GMT Connection: keep-alive Content-Length: 263268 Content-Type: application/javascript ETag: "5fdbec22-40464" Last-Modified: Thu, 17 Dec 2020 23:39:14 GMT Accept-Ranges: bytes ``` == Match existing Nginx config Most of the above was addressed by simply using the same Nginx config as we have in the private infrastructure.git repo for provisioning legacy codeorigin (copied from wordpress-header.conf.erb and wp/jquery.pp, respectively). However, the last two points (charset, and unstable etag), require additional changes. == Nginx: charset The default charset from Nginx differs between Debian and Alpine, possibly due to changes in some of the shared libraries. Fix this by explicitly setting `charset utf-8`. Note that this does (and should) only apply to files with a MIME type in `charset_types` list, which by default contains HTML, JS, and CSS. More importantly, it does not (and should not) cause PNG files and other binary assets to be served with a charset, which would be wrong. == Nginx: E-Tag and Last-Modified This was a tricky one. Git does not store file modification timestamps, which means the on-disk file mtimes are somewhat arbitrarily set to when the file was created by the local Git client. For most files, this means the time you cloned the repository, and for files added later, the time you ran `git pull`. This was de-facto "stable" on the legacy server because it is a persistent server with a persistent git directory (we always use the same clone and just update it to fetch new files after a commit happens). The timestamps themselves don't matter as long as they remain constant for any given file, and they were. This is important because E-Tag is computed in Nginx based on file mtime and size and (unlike Apache) this behaviour is not configurable in Nginx (e.g. to make it compute a content hash instead). And Last-Modified naturally uses the mtime as well. As a workaround, I've added a `touch` command to assign all CDN files a fixed timestamp far in the past. This is safe for us because all files are expected to remain static. Even if we would change a file, it wouldn't propagate to the CDN without a manual CDN API purge (given the all-important high values for Cache-Control max-age and Expires), and after that would not propagate to any browser that has previously fetched it unless the user clears their own cache (given the all-important Cache-Control telling the browser to re-use the file blindly, which is critical for performance). As such, whether the timestamp remains constant or changes after the file is changed, would make no difference for how it propagates. == New state After, with this patch (and verified by a simple test suite). ``` dockerize$ curl -i 'http://localhost:4000/jquery-3.0.0.js' | head -n20 Server: nginx Date: Mon, 16 Aug 2021 23:49:43 GMT Connection: keep-alive Access-Control-Allow-Origin: * Cache-Control: max-age=315360000 Cache-Control: public Content-Length: 263268 Content-Type: application/javascript; charset=utf-8 ETag: "28feccc0-40464" Expires: Thu, 31 Dec 2037 23:55:55 GMT Last-Modified: Fri, 18 Oct 1991 12:00:00 GMT Vary: Accept-Encoding Accept-Ranges: bytes ``` == Misc changes * Remove left-over Nginx config for purge.php. * Remove left-over php-fpm script. * Use our own document root at /var/www/cdn instead of mixing our files into the default /usr/share/nginx/html/ which contains files from the "docker-nginx" project that we may not want to serve blindly. This mainly affects the odd index.html file that could otherwise be served. * Pin the Nginx base image version at 1.x. This is mostly a no-op since they've been on 1.x for 10 years, but if it does rollover we wouldn't want an unmanaged upgrade to a new major version. We could pun this more narrowly at some point, but 1.x should be fine for now. * Invert the config file (let Dockerfile remove code instead of comment out) so that the most critical lines can be easily linted. * Ensure all files listed are redirected correctly, as per https://github.com/jquery/infrastructure/issues/474#issuecomment-844582865 and verify this with an integration test. The expected values are populated based on running `curl` against localhost from a shell on the legacy codeorigin. I specifically did not compare against live code.jquery.com, since we care about matching what we feed to the CDN (although the above problems were surfacing through there as well). Some of the CDN behaviours can't and don't need to be mimicked from the origin. Prior to launch we will (also) compare the live code.jquery.com against the new staging CDN endpoint, but that's separate from the test for the container. Ref https://github.com/jquery/infrastructure/issues/474.
1 parent 02c9820 commit c928ad9

File tree

5 files changed

+398
-182
lines changed

5 files changed

+398
-182
lines changed

Dockerfile

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,37 @@
1-
FROM nginx:alpine
1+
# Allow patch updates
2+
# https://hub.docker.com/_/nginx/
3+
FROM nginx:1-alpine
24

3-
# Install pre-reqs, since we're doing everything in one container for minimum complexity
45
RUN apk add vim openrc
56

7+
# Add CDN assets to the container
8+
#
9+
# This is an early step in the Dockerfile, so that local docker builds
10+
# can reuse this layer and perform a fast rebuild when you only tweaked
11+
# later stuff (nginx config, args, etc.)
12+
COPY cdn/ /var/www/cdn/
13+
14+
# Set a constant file modification timestamp for all CDN assets
15+
#
16+
# Git does not store modified file timestamps, which means the on-disk mtime
17+
# for most files is set to the time of the Git clone. This is a problem
18+
# since the container-based server will be re-created after each commit,
19+
# and we do not want the public "Last-Modified" and "E-Tag" header information
20+
# to roll over after every rebuild.
21+
#
22+
# While Apache has an option to configure how E-Tag is computed (e.g. based on
23+
# content only), Nginx is always based on file mtime and file size.
24+
#
25+
# As a workaround, set a fixed timestamp for all CDN files. This is okay as
26+
# we don't actually utilized Last-Modified or E-Tag for propagating changes,
27+
# we only use them as a way to re-assure browsers that files haven't changed
28+
# and thus reduce bandwidth from needless re-transfers. Given our maximum
29+
# Cache-Control "max-age", it is already the case that a changed file will not
30+
# be seen by the CDN unless we purge it via the CDN API, and not seen by previous
31+
# browser clients until they clear their own caches.
32+
#
33+
RUN find /var/www/cdn/ -type f -print0 | TZ=UTC xargs -0 -P 4 -n 50 touch --date='1991-10-18 12:00:00' {} +
34+
635
# Define the environment variable that will be used in the origin pull magic header
736
ARG CDN_ACCESS_KEY=''
837

@@ -11,19 +40,16 @@ COPY cfg/vimrc /etc/vim/vimrc
1140
COPY cfg/default.conf /etc/nginx/conf.d/default.conf
1241

1342
# If the CDN_ACCESS_KEY environment variable is *not* set, operate in "break glass" mode where the
14-
# container responds to all requests. Otherwise, look for the secret header the CDN adds to origin
15-
# pulls and only allow responses to those requests, and 301 the rest back to the CDN.
43+
# container serves files without restriction. Otherwise, it responds with redirects to requests
44+
# that don't carry an x-cdn-access request header with the correct key.
1645
#
17-
# Note: We're writing directly to the config files because nginx does not currently have a way to
18-
# access environment variables without significant workarounds. Furthermore, the variables are
19-
# required because nginx does not currently support nested if statements.
46+
# Note: We're substituting the config file because nginx does not currently have a way to
47+
# access environment variables without significant workarounds.
2048
RUN if [ -n "$CDN_ACCESS_KEY" ]; then \
21-
sed -i s/CDN_ACCESS_KEY_PLACEHOLDER/$CDN_ACCESS_KEY/g /etc/nginx/conf.d/default.conf && \
22-
sed -i s/##ACTIVATE-XCDNACCESS##//g /etc/nginx/conf.d/default.conf; \
49+
sed -i s/CDN_ACCESS_KEY_PLACEHOLDER/$CDN_ACCESS_KEY/g /etc/nginx/conf.d/default.conf; \
50+
else \
51+
sed -i s/^.*REQUIRE_XCDNACCESS$//g /etc/nginx/conf.d/default.conf; \
2352
fi
2453

25-
# Load the releases into the container
26-
COPY cdn/ /usr/share/nginx/html/
27-
2854
EXPOSE 80
2955

README.md

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,76 @@
1-
# Official project releases
1+
codeorigin.jquery.com
22
=====================
33

4-
This repo is used to build a Docker container that serves the codeorigin site for jQuery and related projects. It is designed to deploy easily, and includes a "break glass in case of emergency" minimal config mode should codeorigin need to be redeployed urgently.
4+
## Add new assets
55

6-
It also contains the files necessary to build and deploy releases.jquery.com on a separate host, which provides an index of the files on codeorigin.
6+
To publish a new release, project maintainers should commit new assets to the `cdn/` directory and push to the `main` branch. The jQuery CDN (code.jquery.com) and releases site (releases.jquery.com) will both automatically rebuild.
77

8-
## Build a local copy of codeorigin
8+
-------
99

10-
### Default, no restrictions (development or emergency mode)
10+
## jQuery CDN
1111

12-
To build a local container (defaults to "break glass" mode):
12+
The jQuery CDN assets are served from a static file server (Nginx), provisioned through Docker.
1313

14-
1. Install Docker
15-
1. Clone this repo, and `cd` into it
16-
1. Build the image: `docker build -t releases ./`
17-
1. Run the container, exposing port 80: `docker run -p 127.0.0.1:80:80/tcp releases`
18-
1. To exit the container, press `ctrl+c`
14+
Prerequisites:
15+
* Docker (see [Docker CE for Linux](https://docs.docker.com/install/#server), [Docker for Mac](https://hub.docker.com/editions/community/docker-ce-desktop-mac), or [Docker for Windows](https://docs.docker.com/docker-for-windows/install/)).
16+
* Git.
1917

20-
### Redirect non-origin pulls to CDN (production mode)
18+
### Local development
2119

22-
To build a local container in deployment mode (redirecting any requests without the magic header that indicates an origin pull), build the container with the header value in an environment variable:
20+
**Test the container**:
2321

24-
1. Install Docker
25-
1. Clone this repo, and `cd` into it
26-
1. Generate a random string for the environment variable: ``CDN_ACCESS_KEY=`openssl rand -hex 32` ``
27-
1. Build the image: `docker build -t prod-releases --build-arg CDN_ACCESS_KEY=$CDN_ACCESS_KEY ./`
28-
1. Run the container, exposing port 80: `docker run -p 127.0.0.1:80:80/tcp prod-releases`
29-
1. To exit the container, press `ctrl+c`
22+
1. Build the image, by running in a clone of this repo:
23+
`docker build -t codeorigin-dev ./`
24+
1. Start a container, exposing port 80:
25+
`docker run --rm -p 4000:80/tcp codeorigin-dev`
26+
1. The site is now running at <http://localhost:4000/jquery-3.0.0.js>.
27+
To stop the container, press `ctrl+c`
3028

31-
Note that you will need to keep track of `$CDN_ACCESS_KEY` and add it to the headers sent for origin pulls. To test whether this is working correctly, you can use `curl`:
29+
**Inspect the container**:
3230

33-
* This should always redirect to `code.jquery.com`: `curl -i localhost/jquery-3.1.1.js`
34-
* This should always deliver a copy of the file (don't forget to set the environment variable in your current shell): `curl -i -H "x-cdn-access: ${CDN_ACCESS_KEY}" localhost/jquery-3.1.1.js`
31+
Run it with a shell entrypoint and set interactive/tty mode by adding the parameters `-i -t --entrypoint /bin/sh`.
32+
Note that when inspecting the container, nginx will not be started.
3533

36-
## Build the production site
34+
```
35+
docker run --rm -p 4000:80/tcp -i -t --entrypoint /bin/sh codeorigin-dev
36+
```
3737

38-
To deploy, first generate the CDN access key. Next, you'll need to configure the container host to build from the Dockerfile in this repository, and use the CDN access key as build arguments. Finally, you'll configure the CDN to send both the Host header and the access key during origin pulls.
38+
**Debug nginx**:
3939

40-
1. Generate the access key: ``CDN_ACCESS_KEY=`openssl rand -hex 16` ``
41-
1. Configure the container host to build from this repo, and set this build variable:
42-
* `CDN_ACCESS_KEY=(Insert the value of $CDN_ACCESS_KEY here)`
43-
1. Add the magic header and the host header at the CDN for origin pulls: `x-cdn-access: (Insert the value of $CDN_ACCESS_KEY here)|Host: (insert URL to app container)`
40+
In `cfg/defualt.cong`, change `error_log … crit` to `error_log … debug` and then build+run as usual.
4441

45-
## In case of emergency
42+
**Test the container in restricted mode**:
4643

47-
If you need to deploy a codeorigin container immediately, or if there are origin pull failures and you're not sure why, deploy the container without configuring the `CDN_ACCESS_KEY` environment variable. The codeorigin server will respond to all requests without redirecting non-origin pulls to the CDN, so this should be only used in case of emergencies.
44+
There is a restricted mode, which responds to unauthorized static file requests with a redirect instead of serving files.
4845

49-
## Build the releases sites
46+
1. Run `export CDN_ACCESS_KEY="$(openssl rand -hex 32)"`
47+
1. Build the image, by running in a clone of this repo:
48+
`docker build -t codeorigin-strict --build-arg "CDN_ACCESS_KEY=$CDN_ACCESS_KEY" ./`
49+
1. Start a container, exposing port 80:
50+
`docker run --rm -p 4000:80/tcp codeorigin-strict`
51+
1. The site is now running at <http://localhost:4000/jquery-3.0.0.js>.
52+
To stop the container, press `ctrl+c`
5053

51-
To build and deploy your changes for previewing in a [`jquery-wp-content`](https://github.com/jquery/jquery-wp-content) instance, follow the [workflow instructions](http://contribute.jquery.org/web-sites/#workflow) from our documentation on [contributing to jQuery web sites](http://contribute.jquery.org/web-sites/).
54+
In the restricted mode:
5255

53-
## Add or update project release files
56+
* Simple requests for static files redirect to `code.jquery.com/*`, e.g. `curl -i 'http://localhost/jquery-3.0.0.js'`.
57+
* Requests with the access key will respond by serving the file: `curl -i -H "x-cdn-access: $CDN_ACCESS_KEY" 'http://localhost/jquery-3.0.0.js'`
58+
* Requests with an invalid key will redirect the same as without: `curl -i -H "x-cdn-access: wrong" 'http://localhost/jquery-3.0.0.js'`
5459

55-
To add a new release or update an existing one, simply commit the new file to the `cdn` directory and merge to the `main` branch. The container will rebuild automatically.
60+
### Production deployment
61+
62+
1. First, generate a CDN access key: `openssl rand -hex 32`.
63+
1. At a hosting platform of your choosing, build a container from the Dockerfile in this repository, and pass the CDN access key as build arguments.
64+
1. Finally, configure the CDN to use the container address as its origin, with special handling to augment origin pulls with a `Host: code.jquery.com` header, and a `x-cdn-access` header with the access key.
65+
66+
### In case of emergency
67+
68+
If you need to deploy a standalone codeorigin site immediately, or if there are origin pull failures and you're not sure why, then deploy the container without any `CDN_ACCESS_KEY` environment variable. The codeorigin container then default to its unrestricted mode (no offload redirects).
69+
70+
-------
71+
72+
## WordPress build
73+
74+
This repository is also used to update the asset catalog at <https://releases.jquery.com/>, which is an auto-generated WordPress site.
75+
76+
To preview changes for a [`jquery-wp-content`](https://github.com/jquery/jquery-wp-content) instance, follow the [workflow instructions](http://contribute.jquery.org/web-sites/#workflow) from our documentation on [contributing to jQuery Foundation web sites](http://contribute.jquery.org/web-sites/).

cfg/30-start-php-fpm7.sh

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)