Serving Phoenix static assets from a CDN

When running your app in production, you can improve performance by serving static assets such as images, CSS and JS from a content delivery network (CDN). The CDN caches your content in servers close to your customers, improving network latency.

Phoenix is fast, but the more requests handled by the CDN, the fewer requests your server needs to handle, allowing it to focus on dynamic content. A CDN like CloudFlare can also protect your app from DDOS attacks.

Serving static assets from Nginx

If you are running your app behind Nginx, configure Nginx to serve the static files. Set the root dir in the vhost to point to the priv dir in the release:

root /opt/myorg/foo/current/priv/static;

Serving assets from a CDN

Many CDNs work as a read-through cache. If the CDN gets a request for a file that is not in its cache, then it contacts the app to get it, then caches it. With others, you have to separately upload assets to the CDN when deploying.

In a simple app deployed to a single server, we deploy the app and its assets together, so the assets are always in sync with the code.

With HTTP-level caching, by definition, the cache will serve an old version of the file. As you make changes to your static assets, you need to make sure that the application uses the version of the assets corresponding to the version of the code that's running.

If you deploy a new version, the code should use the new assets. If you roll back, it should go back to the old version. Similarly, if you are doing a Blue / Green rolling deploy of your app in an auto scaling group, you will have a mix of old and new app versions sharing the CDN.

Building static assets in Phoenix

Fortunately, Phoenix handles the process of generating unique names for your assets. It maps a generic name like /js/app.js in your template into a versioned name like /js/app-8f0317e89884de8b7b3a685928bee5e7.js.

When we update the assets, they get a unique id, and the client will load get the new version. The unique filenames mean that multiple versions of the same file can coexist in the cache.

We can also set the cache lifetime to infinity everywhere, as they will never change. This lets the browser and other caches keep them around longer for better performance.

TODO: cache headers

The first step is to compile your application assets for production

mix deps.get --only prod
MIX_ENV=prod mix compile
MIX_ENV=prod mix phx.digest

This builds assets under priv/static. When we deploy a new release to production, we need to copy the new asset files into the CDN.

For example, we can sync the files to the S3 bucket backing your CloudFront distribution.

aws s3 sync priv/static s3://$CDN_ASSETS_BUCKET

Configuring DNS

In order to use the CDN, Phoenix needs to generate URLs that point to it. First, we set up a DNS CNAME record pointing http://assets.example.com/ to the URL of your CDN.

If you are using Amazon AWS and CloudFront CDN, then you should use Amazon Route53 for your DNS, as it supports a special ALIAS record that works like a CNAME, but follows the underlying resource if it changes.

Route53 also allows you to alias bare domains, e.g. http://example.com/, which is otherwise not allowed. This is particularly useful for high volume sites, where we want to reduce the amount of data transferred. Make a separate domain like http://foocdn.com/ to serve your static assets, and it will not have the cookies associated with your main domain. And you can shave off the subdomain at the front, every byte helps :-).

Configuring Phoenix to use the CDN

Configure your app to use the CDN by setting the static_url parameters in the endpoint config.

See this article for more details.

Getting the user's public IP

When you are running behind a CDN, requests will look like they come from the CDN. In order to get the user's actual IP, we need to look at the headers set by the CDN.

See "Serving your Phoenix app with Nginx" and "Getting the client public IP address in Phoenix" for details.