Build saleor dashboard docker image that accept environment variables
Tags: saleor, saleordashboard, react, docker
Published: 2022-04-25 23:22:00

> This hack is no longer needed as it is implemented in the official docker images, in this [PR](https://github.com/saleor/saleor-dashboard/pull/2473)

Saleor as mentioned in its [landing page](https://saleor.io/) is 
> An open-source, GraphQL-first e-commerce platform delivering ultra-fast, dynamic and personalized shopping experiences.

Saleor is built as separate components
- `saleor-core` which is the graphql api
- `saleor-dashboard` which is the amdin area for the app
- `saleor-storefront` which is the user facing part of the ecommerce platform

Usually, we as developers use the first 2 components as provided by the saleor team and build a custom storefront on top of it 

### The problem
The `saleor-dashboard` is a react app that is build as a static site in a docker container but to do that we have to provide the API_URI at the build time 
so for example if you have 4 environment `development`, `stagging`, `test` and `production` then you will need to build 4 different versions of the dashboard 

i looked a round to see if someone else asked the same question and i found this [issue #1592](https://github.com/saleor/saleor-dashboard/issues/1592)

### My solution
The only answer on the above mentioned issue suggested that modifying the `config.ts` and `index.html` file would do the trick, so i will explain how you can do it manually, then feel free to automate the process 

#### Get the dashboard code
clone  the repo and checkout the version you need to build 
```bash
git clone --depth 1 --branch <ref>  https://github.com/saleor/saleor.git
```
#### Config file
we will create a bash script `env.sh` that read the `$API_URI` env variable and generate a config javascript file, this script will run every time before the docker container started

notice that the docker container will serve the dashboard from a folder called `dashboard` that is why the script will generate the env file in the same folder

```javascript
#!/bin/bash
target_file=./dashboard/env-config.js

touch $target_file
rm -rf $target_file

echo "window._env_ = {" >> $target_file
  echo "  API_URI: \"$API_URI\"," >> $target_file
echo "}" >> $target_file
```

The generated file should look like this 

```javascript
window._env_ = {
    API_URI: "<api-url-from-env>"
}
```

#### Modify `src/index.html`
Edit the main html file of the dashboard and add this script tag on the head 

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <meta name="robots" content="noindex">
    <title>Saleor e-commerce</title>
    <script src="/dashboard/env-config.js"></script>  <!-- add this line-->
  </head>

  <body>
    <div id="dashboard-app"></div>
  </body>
</html>
```
#### Modify `src/config.ts`
now we will read the `API_URI` from the `window` object at the runtime instead of reading it from environment at build time

```typescript
import packageInfo from "../package.json";
import { SearchVariables } from "./hooks/makeSearch";
import { ListSettings, ListViews, Pagination } from "./types";

export const APP_MOUNT_URI = process.env.APP_MOUNT_URI || "/";
export const APP_DEFAULT_URI = "/";
// remove this line
export const API_URI = process.env.API_URI;

// add these 2 lines, typescript build would fail without the @ts-ignore
// @ts-ignore
export const API_URI = window._env_.API_URI;
....
....
```

#### Modify `Dockerfile`
for the docker file we need to do 2 things 

1. copy the `env.sh` script into the docker image root

2. change the command to run the `env.sh` before starting the container

```Dockerfile
FROM node:14 as builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ARG APP_MOUNT_URI
ARG API_URI
ARG STATIC_URL
ENV API_URI ${API_URI:-http://localhost:8000/graphql/}
ENV APP_MOUNT_URI ${APP_MOUNT_URI:-/dashboard/}
ENV STATIC_URL ${STATIC_URL:-/dashboard/}
RUN STATIC_URL=${STATIC_URL} API_URI=${API_URI} APP_MOUNT_URI=${APP_MOUNT_URI} npm run build

FROM nginx:stable
WORKDIR /app
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/build/ /app/
# Add the following lines
COPY ./env.sh .
RUN chmod +x env.sh
CMD ["/bin/bash", "-c", "/app/env.sh && nginx -g \"daemon off;\""]
```
No you can build `saleor-dashboard` image that take the `API_URI` from your deployment environment 

#### Note
For me to not repeat the above steps with every new version of the dashboard, i created a gitlab CI/CD pipeline to do this automatically for whatever version/tag/ref i specify but unfortunately  this is something i can't share