Pixies, part 12: Ship It!


Environment values in the browser

So we want dynamically specify the api URL in the frontend application running in the container, preferably taken from the environment values. The thing is that the frontend application runs in the browser on the user’s machine. You wouldn’t want (and couldn’t) to hijack the user’s browser to set some environment value there, obviously. So what do we do? Remember, when we made our nginx to listen on the PORT specified with the environment variable - we essentially modified the nginx’s config in the boot.sh script just before starting it. We are going to do the similar thing here. First let’s externalize the configurable settings into a separate file. Create a directory static and add the file env.js

mkdir static
touch static/env.js

We are going to keep all configurable settings in the global object window.settings. So add the following code to the env.js

window.settings = { backendUrl: 'http://localhost:9090/api/' };

Now, we want the env.js to be read once the index.html is loaded. Add the reference to this file, just before the closing <body> tag

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Pixies</title>
  </head>
  <body>
    <section id="root"></section>
    <!-- new -->
    <script type="text/javascript" src="env.js"></script>
  </body>
</html>

In the App.js, instead of hardcoding the backend Url, read it from the window.settings object:

// ...
useEffect(() => {
    // changed
    const url = window.settings.backendUrl;
    // ...

Ok, this should work. Having said that, we are not going to deploy the env.js file in production, but rather generate it dynamically. In the development though, we want it simply to be copied to the build directory. Of course, there is webpack plugin for that.

npm install copy-webpack-plugin --save-dev

Enable the plugin in webpack.config.js

//...
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// new
const CopyPlugin = require('copy-webpack-plugin');
// ...
plugins: [
    // ...
    new CopyPlugin([
      {
        from: 'static',
        to: '.'
      }
    ])
  ]

This configures the Copy plugin to copy everything from the static directory to the build directory. We can now start the frontend app in the dev mode and it should work:

npm start

Check in the javascript console that the windows.settings object has the backendUrl property, as expected. Ok, that is for the development mode. For the production we’ll modify frontend’s boot.sh file with that:

#new, at the neginning
echo "window.settings ={backendUrl: '$BACKEND_URL'};" > /usr/share/nginx/html/env.js
# ...

This reads the BACKEND_URL environment file and dynamically inject it to the env.js file.

trying it on.

Let’s add this variable to our frontend service in the docker-compose.yml

 frontend:
    build:
      context: ../frontend
    ports:
      - 9091:80
    environment:
      - PORT=80
      # new
      - BACKEND_URL=http://localhost:9090/api/
    depends_on:
      - backend

All right, rebuild the containers and give it a spin:

docker-compose up -d --build

The working frontend should be available at at http://localhost:9091

deploy to Heroku

If you have followed along, you have our initial frontend deployed to heroku. We will need its address for configuring the backend. So go to the frontend directory and do

heroku open

Your old frontend will launch, copy the URL. Mine is https://hidden-sands-09399.herokuapp.com/. Yours will be different. Switch to the backend directory and create a heroku app

heroku apps:create

So now we have two heroku apps, for the frontend and for the backend, like we planned. For our backend we need a Postgres. Heroku offers Postgres add-on, Let’s enable it:

heroku addons:create heroku-postgresql:hobby-dev

This will enable Postgres add-on on the free hobby-dev plan. Check the configuration for your Heroku app:

heroku config

You will see that the DATABASE_URL environment is set. How nice, Exactly what we need! Let’s set the remaining required config variables

  • SECRET_KEY, set it to something more secure naturally
heroku config:set SECRET_KEY=hushush
  • ALLOWED_HOSTS, set it to the .herokuapp.com
heroku config:set ALLOWED_HOSTS='.herokuapp.com'
  • CORS_ORIGIN_WHITELIST, that should be set to your Heroku frontend URL
BACKEND_URL: https://protected-basin-26908.herokuapp.com/api/
heroku config:set CORS_ORIGIN_WHITELIST='https://hidden-sands-09399.herokuapp.com'

Push the backend container to Heroku

heroku container:login
heroku container:push web
heroku container:release web

This should do it, the backend is deployed on Heroku now. We need to do things with the backend now:

  • initialize the database(that is run migrations)
  • create a superuser
heroku run python manage.py migrate
heroku run python manae.py createsuperuser

Do

heroku open

Tweak the URL so it ends with /admin (remember, the backend does not listen on the root URL!) Add a few photos, and copy the URL of your backend. The last step is to update and configure your frontend Heroku app. go to the fronted directory and configure the address to your backend. Here’s mine, yours will be different

heroku config:set BACKEND_URL='https://protected-basin-26908.herokuapp.com/api/'

Now, re-push your frontend container:

heroku container:rm web
heroku container:push web
heroku container:release web

Do

heroku open

Enjoy your frontend pulling the photos' info info from the backend. (you may need to reload the page as the browser might have cached the old frontend)


See also