Hacker News new | past | comments | ask | show | jobs | submit login
Injecting environment variables into static websites using Nginx (innoq.com)
65 points by kaeruct on Sept 30, 2021 | hide | past | favorite | 28 comments



Just for entertainment... In 1995 or so, netscape server came with server side includes so you have your navigation come from a separate page. You'd put this in your html

<!--#include file="header.html"-->

and the header.html file would be just inlined into the page.

You could also write plugins for Netscape server in c to do whatever else you liked to the html page before serving it to the browser.

It was all blazingly fast, the compiled binary code was in memory and unix was doing a good job of keeping our frequent pages (out of about 30,000 in total) in memory too. We easily saturated our datacenter's networking with it. All with mid range for it's time but now hilariously puny server.


I recently had to solve a similar problem on a project and did so in a different manner, but with largely the same results, using *nix's `envsubst` and nginx's `subs_filter`. I really like the SSI approach, but I'll share our approach as well as it covers it slightly differently.

We had a set of static frontend apps (React) that were built in CI, and which had to support being served either out of S3 buckets in a variety of environments, or in walled-off on-prem environments, and therefore had to support a variety of API endpoint URLs. There was an 85% likelihood that the endpoint URL would be one main one (our primary distribution platform), but the other 15% could be anything.

I set up a global variable attached to `window` (gasp!) in the frontend's index.html that would point to the API endpoint, and by default our CI build would spit out the common scenario URL. When doing local development, the URL was supplied via an environment file, which worked perfectly.

For all other production scenarios we served the app out of a Docker container. At container launch, we passed in via env vars the proper endpoint URL, and a script would absorb the passed env var and embed it into the nginx config file using `envsubst` like the OP's article. This config file was configured using `subs_filter` to find our global variable in the index.html file and replace its contents with that of the env var, which was now hardcoded in a sense into the file.

This setup was a bit convoluted, but it allowed us to not have to change anything while in local development, be able to change the endpoint URL each time the container was launched without any rebuild, and didn't require any additional requests to a configuration script. It was the best fit in our situation to allow local development to continue unchanged while also supporting pretty much any future scenario.

Hope that might spur some ideas for someone.


Was looking at doing something similar but settled on a slightly different approach:

1. Bundle the app image with a “container.json.template”

2. Have the UI request this file first, along with a preload in the head

3. Use Nginx envsubst to populate the config.json from env vars on container start

It’s an additional HTTP request and templating JSON via string interpolation isn’t fun but it was quite simple and kept the build clean with config injected at run time.

Given the interpolation problem (I.e chaos when someone uses a number value and it’s undefined), I’d probably ditch env subst and perhaps do something else to build the JSON.


Oh wow, SSI. Now that's a term I don't remember hearing for almost 20 years.

Surely you can still find some old site from somewhere that uses it, but has someone actually used it even somewhat lately & what use case you had? For most cases where it made sense you would probably just use some static site generator and deploy different version if required. Or do the replacing during the publishing phase.


I use it on my private site. Nothing fancy, just a couple of static pages. I use it for including header and footer.

PHP is totally overkill, and even a site generator feels overkill for this purpose; now I can just throw together some html and upload, no need for running a script.

Also it's a bit of nostalgia (the reason I implemented it a cople of years ago) since ssi was my first contact with web development back in '96 or whenever :)


Footers, headers, update date.


I use it in a "static-dynamic hybrid" site generator.

There is a pure static mode, when you pre-generate all the HTML.

Then there is a hybrid mode, with basic templating of time/clock. SSI can also be used to update pages.

When PHP is available, it can very closely approximate a dynamic site, with pages being pre-generated just in time, but then served to all subsequent requests.


I used it to show the IP address of the visitor for my own "what is my ip". Didn't need anything but an HTML file


The microfrontend hype kind of revived SSI a bit, but I would say it's still very much niche.



In fact, the docs on the Caddy website are actually just Markdown being rendered by the Caddy binary itself. It's literally just Caddy plus static files. (There is a backend though for the JSON docs, because those are dynamically generated from static analysis on Go code)


The cool thing about SSI is that it is one of those "traditional" or "orthodox" Web formats that's supported by pretty much every non-minimal webserver, so if you write your templates carefully (avoiding edge cases) your app can be installed on any web server also, and the only difference is the web server's config template.


I had to do exactly that in a large Angular project, dince there was no way to provide environment variables available at runtime.

Angular provides an environment mechanism at buildtime, so they expect us to create a docker image per environment, which is not our practice with other microservices in our pipeline. We’re used to kubernetes configMaps.

We used a docker docker runtime script + envsubst as above. However we used it to provide key/values to the window object (inside a template script tag), to be picked up by a proper Angular “AppConfig” service.

I’m not very happy with this solution as it might scare the next maintainers, specially if they don’t know about our ci/cd and backend. The source code is also peppered with little comments about this workaround, which is not ideal.


Interesting. I usually just do a two liner with envsubst

envsubst < template > parsed

executable $@


Just because you do SSG doesn't mean you can't run _any_ javascript. This seems like a lot of work to just put a little JS button on the site.


You still need to inject values from the environment into the page source at build time, whether you're using Javascript or not. And if you're already doing that, why add Javascript on top when all you need is an anchor?


Exactly. I couldn't have solved this with JS only.


We can also create a mapping between the hostname<>configs to have env specific configs.

I've written more about it here: http://www.sheshbabu.com/posts/frontend-config-management/


Correct me if I am wrong, but this all just sounds like we're inventing PHP again.


The difference is SSI is extremely fast and quite limited. You’re not supposed to write your entire website in it.


I'd be interested in seeing what the performance difference between the NGINX SSI and <?php echo $_ENV['SIGN_OUT_URL'] ?> is. Just because PHP _can_ be slow, doesn't mean it _must_ be slow.


Also SSIs predate PHP by some time


Uh, how about using a proper static site generator and call it a day?


> In our case, the SSG tool is Jekyll, so adding its entire Ruby toolchain would defeat the purpose of using NGINX: being as lightweight as possible. Additionally, this huge image would create more attack surface. So, we quickly eliminated this option.


I don't think they understand how SSG works. You generate the site locally and then publish the artifacts. Its not like Jekyll is running on their server. There are also lighter weight SSGs, like Hugo. You can also run Jekyll on an alpine image for CI/CD and it is pretty small. My Hugo Alpine image is a mere 10MB. Once it is cached by CI/CD then it isn't being re-downloaded.


I think they do understand how ssg works. The issue they’re trying to solve is how to inject env-specific configuration at runtime. Regardless of which ssg you use you still need a way to vary these values at runtime. I don’t think using the ssg itself is a very efficient solution. Things like envsubst work well to template env variables into static assets or server config at image startup.


The issue is not so much about Jekyll vs. Hugo, because in our situation we also had to include other renderers like Graphviz, which bloats the build image considerably.


No SSG can access information that is only available at serving time. Otherwise it would not be a SSG.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: