cs50/server

cs50/server is a Docker image on Docker Hub, implemented with this Dockerfile, with which you can (easily!) serve websites with, optionally, back ends implemented in JavaScript, PHP, Python, or Ruby. (We use it to serve CS50’s own apps on AWS Elastic Beanstalk!) Essentially, it’s a lightly customized installation of Passenger, an app server, to which we’ve added support for PHP (for some of CS50’s older web apps). It also facilitates configuration of Nginx, the web server used by Passenger in in Standalone mode, via two files, httpd.conf and server.conf. The image itself is based on cs50/cli, which, in turn, is based on Ubuntu 22.04, a popular distribution of Linux.

Usage

Assuming you already have Docker installed, base your own Dockerfile on cs50/server as follows, exposing TCP port 8080, the server’s default:

FROM cs50/server
EXPOSE 8080

Then ensure your app is structured as follows.

  • If your app’s back end is implemented in Meteor

    • in bundled/packaged mode, ensure you have a file called app.js (your app’s entry point file) in the same directory as your Dockerfile.

    • in non-bundled/packaged mode, ensure you have a file called .meteor in the same directory as your Dockerfile.

  • If your app’s back end is implemented in Node.js, ensure you have a file called app.js (your app’s entry point file) in the same directory as your Dockerfile.

    • If you have a file called package.json in the same directory as your Dockerfile, npm install will be run automatically when your image is built.

  • If your website’s back end is implemented in PHP, ensure that you have a directory called public in the same directory as your Dockerfile, inside of which are any PHP files meant to be served publicly.

  • If your website’s back end is implemented in Python, ensure you have a (WSGI) file called passenger_wsgi.py, formatted as prescribed, in the same directory as your Dockerfile.

    • If you have a file called requirements.txt in the same directory as your Dockerfile, pip install -r requirements.txt will be run automatically when your image is built.

  • If your website’s back end is implemented in Ruby (or Ruby on Rails), ensure you have a file called config.ru, formatted as prescribed, in the same directory as your Dockerfile.

    • If you have a file called Gemfile in the same directory as your Dockerfile, bundle install will be run automatically when your image is built.

  • If your website does not have a back end, only a front end implemented in HTML (presumably with CSS and/or JavaScript), ensure that you have a directory called public in the same directory as your Dockerfile, inside of which are any HTML (and CSS and/or JavaScript) files meant to be served publicly.

Configuration

APPLICATION_ENV

If you set an environment called APPLICATION_ENV to a value of dev, as via a docker-compose.yml file, cs50/server (and, in turn, Passenger) will restart your application’s back end after every HTTP request (by creating a temporary file called tmp/always_restart.txt), thereby ensuring that any changes you make to files are noticed (and not cached).

Entry Point

By default, cs50/server looks for a file called app.js, config.ru, .meteor, or passenger_wsgi.py per its Usage. To configure cs50/server to use some other file as an app’s entry point, adjust your Dockerfile as follows, where app_type is as prescribed and startup_file is the relative path of the file to use.

FROM cs50/server
EXPOSE ####
...
CMD passenger start --app-type app_type --startup-file startup_file

Nginx

You can customize cs50/server’s installation of Nginx by adding directives as follows.

  • To add directives to Nginix’s http context, put them in a file called http.conf in the same directory as your Dockerfile. Do not surround them with http { and }.

  • To add directives to Nginix’s server context, put them in a file called server.conf in the same directory as your Dockerfile. Do not surround them with server { and }.

rewrite

To redirect, say, /surprise (and /surprise/) to https://youtu.be/dQw4w9WgXcQ, create a file called server.conf in the same directory as your Dockerfile, the contents of which are as follows.

rewrite ^/surprise/?$ https://youtu.be/dQw4w9WgXcQ redirect;

To redirect one domain to another (e.g., www.cs50.harvard.edu to cs50.harvard.edu), create a file called server.conf in the same directory as your Dockerfile, the contents of which are as follows.

if ($http_host = www.cs50.harvard.edu) {
    rewrite (.*) https://cs50.harvard.edu$1;
}

try_files

To route all requests (that aren’t for actual files or directories) to public/index.php, create a file called server.conf in the same directory as your Dockerfile, the contents of which are as follows.

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

To route all requests (that aren’t for actual files or directories) to public/index.html (as you might for a JavaScript-based single-page app), create a file called server.conf in the same directory as your Dockerfile, the contents of which are as follows.

location / {
    try_files $uri $uri/ /index.html;
}

Port

By default, cs50/server uses TCP port 8080. To configure cs50/server to use some other port, adjust your Dockerfile as follows, where #### is your choice of ports:

FROM cs50/server
EXPOSE ####
...
CMD passenger start --port ####

Static Files

By default, cs50/server assumes that your app’s static files are in public, which is assumed to be in the same directory as your Dockerfile. To configure cs50/server to look in some other directory, configure your Dockerfile as follows, where path/to/directory is the relative path to that directory:

FROM cs50/server
...
CMD passenger start --static-files-dir path/to/directory

Notes

Caching

By default, HTTP responses from apps served by cs50/server are not cached by browsers (or proxies) because the image adds

Cache-Control: no-cache, no-store, must-revalidate
Expires: 0
Pragma: no-cache

to those responses.

To allow responses to be cached, create a file called server.conf in the app’s root containing the below, which will remove those headers:

more_clear_headers 'Cache-Control' 'Expires' 'Pragma';

HTTPS

If cs50/server detects that it’s running behind a load balancer, whereby X-Forwarded-Proto (an HTTP header) is set, and the value of that header is http (the implication of which is that a client’s request used HTTP instead of HTTPS), cs50/server will redirect the request to use HTTPS.

Inline Frames

By default, apps based on cs50/server cannot be iframed in other sites, as the image adds

Content-Security-Policy: frame-ancestors 'self'

to HTTP responses. To allow an app to be iframed by another site, create a file called server.conf in the app’s root containing the below, which will remove that header:

more_clear_headers 'Content-Security-Policy';