Nginx 1.19 supports environment variables and templates in Docker
Marco Franssen /
6 min read • 1105 words
In this blog I want to show you a nice new feature in Nginx 1.19 Docker image. I requested it somewhere 2 years ago when I was trying to figure out how I could configure my static page applications more flexibly with various endpoints to backing microservices. Back then I used to have my static pages fetch a json file that contained the endpoints for the apis. This way I could simply mount this json file into my container with all kind of endpoints for this particular deployment. It was some sort of service discovery mechanism I applied back then by having my React application fetch this json file as the first step.
Now since the Release of Nginx 1.19 Docker image it is finally possible to do this using Environment variables which enables you to use Nginx images in a more immutable fashion. By doing so you can package your application in such way it actually also works with the React development server proxy. In a React project you would for example define this in following way in your package.json
.
This would make all calls to http://localhost:3000/api
be proxied to http://localhost:5000
which is very convenient, because you can code all api calls as relative paths (e.g. /api/todos
). How awesome would it be if I can simply deploy my app like this in an immutable Docker container where I only would have to specify the endpoint of my api.
Good news! As of May 2020 you can do this.
Setting the scene
We have a React application that fetches todo items from an api. During development we are using the following setup to reverse proxy these calls to our api backend service.
| Endpoint | Service | | ------------------------- | --------- | | http://localhost:3000 | React SPA | | http://localhost:5000 | TODO API |
In our package.json
we configured this for our development flow as following.
So now when I want to deploy this app to production, I would love to configure this TODO API endpoint based on the ip address or DNS entry it will get in for example my Kubernetes cluster. Let's have a look how we can achieve that by defining a Nginx template.
In above template I defined some caching rules for static assets and I configured gzip compression. I also defined an upstream where we reverse proxy the urls that start with /api
and a variable to define the port on which Nginx runs. See the below abstract.
As you can see we can define variables using following syntax ${VAR}
. These variables will be replaced when the Docker container starts with the value as you defined them in the environment variables.
Now we have the template, we can define our Dockerfile
.
In the Dockerfile I am using a multi-stage build to have a Docker image which is as small as possible.
- First we install our package dependencies
- Next we build the React SPA
Now with the output of this intermediate Docker image we will create a new container from the nginx:1.19-alpine image.
-
We set sane defaults for the 2 environment variables we defined in our
default.conf.template
.host.docker.internal points to our Docker host (your Mac, or Windows). Using this default I can run the image and connect it by default to the service running on my localhost which allows for easy debugging of the API in a development flow.
-
Then we copy our nginx/templates into the templates folder (you could use more templates and use includes for example)
-
Last we copy the output of
yarn build
from our intermediatebuild
image into the server root as defined in our template.
With all of this in place we can now build our Docker image.
How does this work
By default the nginx entrypoint replaces all variables found in /etc/nginx/templates/*.template
with their respective values using envsubst
. The results of this substitution are written to /etc/nginx/conf.d
.
So make sure you copy the file in the templates folder and ensure its extension template
.
Running the image
Now we can run the image as following using the defaults which still connect to our api running on our localhost via host.docker.internal
.
If you want to run all in docker-compose you can run as following. In the following docker-compose we route the api requests via the internal docker network using the containers default dns record.
When doing a Kubernetes deployment you simply point the TODO_API
variable to the Service deployed on top of your pods.
Now go ahead and try this for yourself. Feel free to comment below if you require help.
Thanks for reading this blog. Please do share it with your friends and colleagues.
Who doesn't want to have a immutable, configurable static web application?