First look: static asset caching

One comment we keep getting from people building super interesting fullstack apps is: “I don’t want to have to deal with a CDN when I deploy”.

Fly.io isn’t a CDN, but we do have some CDN-like infrastructure that we can do fun things with. Today’s fun thing is [[statics]] in fly.toml. Add a config section to your app like this:

[[statics]]
guest_path = "/app/web/dist"
url_prefix = "/static"

When you deploy, we’ll pull /app/web/dist out of your packaged app, then serve requests that to https://<app>/static from caches on our edge servers. You don’t have to worry about cache purging or expiration or anything, each deploy just does the right thing. And we keep a few versions around so you don’t get blank pages mid deploy, people who request outdated static URLs will get what they expect.

Requests to static files fall through to your app if they don’t exist, so you can even use this for /index.html and have your app servers handle /graphql.

This is not a replacement for Netlify! And if you really need a CDN, it’s not going to solve all your problems (but, like, most people don’t need CDNs). It’s very specifically designed for assets that get deployed alongside full stack Phoenix/Rails/Laravel/Django apps.

16 Likes

Some things to know going in:

  • There are likely to be some rough edges, and when you run into them, please let us know and we’ll hunt them down.

  • Static assets are currently tied to instances that you deploy (we fish them out of the container image you deploy, and serve them off the worker host you’re deployed on). This is less-than-optimal if what you’re looking for is mostly using us as a static asset host, but solves the problem of “I don’t want to have to roll a container image with both my app server and an nginx or something”.

If you could get mileage out of something more ambitious than this, please tell us!. We’re situated to expand on this, but prioritizing what we’re working on based on what y’all ask for.

Regardless, if you kick the tires on this, thank you!

1 Like

So I’ve just tried this out - I think it all works, as in nothing is broken, but I’m not really sure [[statics]] had any effect. How can I tell?

We should add a distinguishing header here; I’m on that. In the meantime: I just test by serving assets that my app server doesn’t serve itself. :slight_smile:

I could use something more ambitious if you are thinking about it.

Currently about to move some Google cloud functions to fly.io. They generate images based on parameters in the url (along the lines of imgix, imagekit.io etc). At the moment they are fronted by google’s CDN via the load balancer configuration.

Once I moved them to fly, I was planning to front with AWS cloudfront or possibly look at using nginx/varnish inside fly.

End goal would be a cache layer that honors the cache header from the origin , and caches at the edge (for at most a day).

Long term it would be good to get metrics/bandwidth usage information from the cache layer, but not an initial requirement.

2 Likes

This looks neat, tried it and it works as expected!

One thing I noticed is that there don’t seem to be any cache-control headers in the response.

That’s a good point. We should probably send basic revalidate headers.

I second the motion for obeying cache control headers from the origin.

In particular, I have found Fastly’s support for the Surrogate-Control header useful avoiding something getting stuck in browser cache forever. Maybe not applicable to the ‘js/css assets’ use case, though.

Another ‘ambitious’ thing: I’ve seen lots of apps using a passthrough CDN merely to split requests out by path to different origins. So for example, mayonnaise.app would route to a static marketing site, but mayonnaise.app/joshua would route to a web application.

Being able to do this and get asset caching without using a CDN product would be a win!

This is related to my other post about path-based routing. Only here, we would be routing organization-wide instead of between components within an app.

How much of a performance penalty would there be to set url_prefix = "/" for a dynamic app?

It should be negligible. Static files looks are sub millisecond.

1 Like

Is it possible to use wildcards or route patterns in url_prefix?

Suppose in my application I have URLs that look like this:

  • /jobs/34234523/graphql GraphQL API to query a specific job
  • /jobs/34234523/index.html UI that issues API calls to the the graphql endpoint

I’d prefer to have a [[statics]] block that looks like:

[[statics]]
guest_path = "/app/web/dist"
url_prefix = "/jobs/*/"

This way users can just navigate to /jobs/34234523 and see a UI specific to that job.

This is a pattern I’ve adopted in a number of my projects. It strikes a balance between:

  • clean URLs,
  • simple to write UI,
  • and reasonably decoupled backend API

It’s not currently possible, but it’s a reasonable request. I could also just use regular expression matching here, I guess?

Yeah anything along those lines would be great. I think this idea of “interpret the URL in javascript after page load” is a fairly powerful one.

I’ve used it with Netlify via their routing/rewriting/proxying rules: Rewrites and proxies | Netlify Docs

I think this is not at all hard for us to implement, but don’t hold me to that until I deploy the official :zap: :fish_cake: we’re doing this right now :fish_cake: :zap: declaration. I’m already working in this code today, so I might be able to get this on my docket relatively quickly.

1 Like

I’ve configured with:

[[statics]]
  guest_path = "/app/static/"
  url_prefix = "/"

But I don’t think static files are being served from the cache; I’m not seeing anything in the HTTP CACHE RATIOS section in Metrics. Should hits be logged there?

@thomas Do cache hits identify themselves with a response header?

They don’t right now! At present, the only thing this feature really does for you is keep you from needing to run a static asset server in your container; we put it together because that seemed easier than talking people through writing multi-process Dockerfiles.

But that’s changing as we speak! I’ll have more to talk about w/r/t caching and tracking in the next couple days.

(If you’ve got an app that relies on statics to deliver content, and isn’t functioning, that’s news, and definitely let us know.)

I was wrong about the caching! Look at me creating scope creep.

How do we make this work on a Phoenix App ? I can’t find the static file looking in /app

If you’re using an Elixir Release based deployment like the one in the guide, then static assets are stored in /app/lib/[your-app]-[version]/priv/static in folders which would have to be listed separately.

3 Likes

Hey @fly guys, I didn’t really get an answer to this question, so wanted to give it a bump.

Right now, the HTTP CACHE RATIOS metrics box across all my apps (and accounts) show No data available despite most of them using the new Statics config.

Is that to be expected? If not, what should be showing there?