Skip to content

Commit

Permalink
Improve documentation. Use react app version from build time variable.
Browse files Browse the repository at this point in the history
  • Loading branch information
Nospamas committed Dec 16, 2024
1 parent 505c0ac commit 651b941
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 54 deletions.
2 changes: 1 addition & 1 deletion .env.development
Original file line number Diff line number Diff line change
@@ -1 +1 @@
PUBLIC_URL=http://localhost:30503
PUBLIC_URL=http://localhost:3000
51 changes: 51 additions & 0 deletions docs/developer/build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Building the project

There are 3 modes of operation for the application which take slightly different build steps:

### Local

Everything in the project should be set up for easy development with defaults provided that allow
execution without modification to configuration. This execution is done via `npm run start`. The
project is then built via create react app and a local development server is started.

Local config is provided via [public/config.js](../public/config.js) and is loaded automatically
as a static javascript file via the local development server.

Public URL is overridden by the `.env.development` file to our expected `http://localhost:3000`

### Local Docker

Testing for deployment involves building in production mode and setting up a container as we will
in production. This allows us to ensure that dependencies are met and gives us a portable artifact
that we can set up on any docker capable machine and expect to work.

Creating the container can be done via the `make image` command. This command executes `npm run build`
creating a static version of the website. `process.env` variables are baked into the files at this
time, so it should be avoided for evironment specific configuration use. These static assests are in
the `build/` folder. Once built the [Dockerfile](../../docker/Dockerfile) pulls in these files along
with dependencies to generate a docker image.

Running the created docker image can be done via `make up`. This brings up the image based on the
specification in the [docker-compose.yaml](../../docker/docker-compose.yaml). This specification also
overrides our local development configuration values by mounting an alternative configuration. Two examples
are provided `config.bc.js` and `config.ynwt.js` representing our two common production versions. `bc`
is used by default.

`PUBLIC_URL` is handled in two steps. During the build process we define a replacement value in `.env.production`
which is injected into any locations where the public URL is required. When the container starts we replace
these instances with the public URL defined in whatever `/app/config.js` within the container has for the
`PUBLIC_URL` value. The specific implementation of this replacement can be found in the
[entrypoint.sh](../../docker/entrypoint.sh) file which is used as the default entrypoint for the container
when it starts.

### Production Docker

Production docker essentially follows the same steps as above (what good would a local test be otherwise!)
but is executed via a github workflow. The resulting image is uploaded to our
[docker hub](https://registry.hub.docker.com/r/pcic/station-data-portal-frontend) for use where desired.

Specific steps are defined in the [github workflow](../../.github/workflows/docker-publish.yml) file.

When running in production we need to provide environment specific config, this config will closely
resemble the templates defined in the `config.bc.js` and `config.ynwt.js` files noted above and should be
mounted to `/app/config.js` within the container.
72 changes: 20 additions & 52 deletions docs/developer/production.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,50 +10,25 @@ builds and tags a Docker image on each commit. The image name is

### Configuration, environment variables, and Docker

It is best practice to configure a web app externally, at run-time,
typically using environment variables for any simple (atomic, e.g.,
string) configuration values.

Here we run into a problem introduced by CRA:
CRA injects environment variables only at _build time_, not at run time.
["The environment variables are embedded during the build time. Since Create React App produces a static
HTML/CSS/JS bundle, it can’t possibly read them at runtime."](https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables).

We deploy our apps with Docker. A natural approach to build (`npm build`)
the app as part of building the image, and then just serve it from a
container. Because of CRA's build-time injection of environment
variables, such Docker images cannot be configured at run-time (i.e.,
when a container is run). Only static, build-time environment variables
are available to CRA apps inside such images.

It takes some extra effort to inject run-time environment variables (or
configuration generally) into Dockerized CRA applications. There are
two basic approaches:

1. Build the app, and inject run-time environment variables, as part of
the image run (i.e., the commands run by the image include building
the app, which at that point has access to environment variables
provided via the `docker run` command).
-This is simple but it means that containers are slow to start up
and contain a lot of infrastructure (Node.js, etc.) needed to build
the image. This isn't an issue for us, because we don't start many
instances and we don't do it often.

2. Fetch the environment variables (or a configuration file) from the server.
- This approach has several variants, which are outlined in this
[CRA issue](https://github.com/facebook/create-react-app/issues/2353).

A key requirement is to be able to configure at run time the the URL at
which the app is deployed. CRA provides a (build-time) environment
variable for this, `PUBLIC_URL`.

Option 1 makes setting `PUBLIC_URL` simple and requires no change to the codebase;
as noted, we don't care about the cost of using such containers.

Option 2 makes setting `PUBLIC_URL` _much_ harder to accomplish, and
would require significant changes to the codebase.

Therefore we use option 1.
It is best practice to configure a web app externally, at
run-time, typically using environment variables for any simple
(atomic, e.g., string) configuration values.

CRA makes this a little challenging, but we use the following
build flow to allow us to inject this configuration.

1. Configuration information is stored in `public/config.js`.
This file is mounted in our docker containers with environment
specific configuration options.
2. Avoid use of `process.env`. While convenient, these variables
are build time required so can't be used for environments unless
we build at run time. Building at runtime incurs a significant
time delay, so shouldbe avoided.
3. Use `window.env` (defined in config.js) as an alternative, any
environment specific information is appropriate here.
4. `PUBLIC_URL` is special and is handled using a replacement
during docker container startup. A full explanation can be found
in the [build](./build.md) documentation.

### Deployment

Expand All @@ -63,11 +38,4 @@ image using `docker-compose`. You may wish to copy and modify
`docker-compose.yaml` to construct a production deployment.

Note: All **deployment environment variables**, except `REACT_APP_APP_VERSION`,
are provided by `docker/docker-compose.yaml` (and any `*.env` files it
names). `REACT_APP_APP_VERSION` is set during image build (see
`.github/workflows/docker-publish.yml`), and it should not be set
otherwise.

This is different from **development environment variables**, which are
provided by the `.env.*` files found in the project root directory.
(Also, `REACT_APP_APP_VERSION` is not set for development.)
are provided by `docker/docker-compose.yaml` via the `/app/config.js` file mount.
2 changes: 1 addition & 1 deletion src/state/query-hooks/use-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const fetchConfig = async () => {
checkMissingKeys(config);

// Extend config with some env var values
config.appVersion = window.env.REACT_APP_APP_VERSION ?? "unknown";
config.appVersion = process.env.REACT_APP_APP_VERSION ?? "unknown";

// Extend config with some computed goodies
// TODO: Store shouldn't know about data presentation
Expand Down

0 comments on commit 651b941

Please sign in to comment.