Skip to content

Commit 90ca58b

Browse files
committed
set up unified frontend/middleware in production without need for reverse proxy
1 parent 61081a3 commit 90ca58b

File tree

11 files changed

+80
-59
lines changed

11 files changed

+80
-59
lines changed

.env.prod.example

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
STEMMAREST_ENDPOINT=http://stemmarest:8080/stemmarest
22
STEMWEB_ENDPOINT=http://stemweb:8000
3-
STEMMAWEB_MIDDLEWARE_URL=http://localhost:8888/stemmaweb/requests
3+
STEMMAWEB_STATIC=stemmaweb
44

55
GUNICORN_WORKERS=4
6-
GUNICORN_BIND=0.0.0.0:3000
6+
GUNICORN_PORT=3000
7+
GUNICORN_BIND=0.0.0.0:${GUNICORN_PORT}
78

89
# Generated via `python -c 'import secrets; print(secrets.token_hex())'`
910
SECRET_KEY=4ba46cc40f09d2a79b541b0d82584fa330e5dd119260a217749570bf648a0f8d
1011

1112
# Get these from the Google API console
1213
GOOGLE_CLIENT_ID=
1314
GOOGLE_CLIENT_SECRET=
14-
STEMMAWEB_FRONTEND_URL=http://localhost:8888/stemmaweb/
1515

16-
# Get these from the reCAPTCHA Admin console
17-
RECAPTCHA_SITE_KEY=
16+
# Get these from the Github API console
17+
GITHUB_CLIENT_ID=
18+
GITHUB_CLIENT_SECRET=
19+
20+
# Get this from the reCAPTCHA Admin console
1821
RECAPTCHA_SECRET_KEY=
1922

2023
LOG_LEVEL=INFO

Dockerfile

+25-13
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
1-
# Building on top of the local middleware image
1+
# Create the static frontend bundle
2+
FROM ubuntu:latest AS frontend
3+
WORKDIR /usr/src/frontend
4+
ENV DEBIAN_FRONTEND noninteractive
5+
RUN apt-get update && apt-get install -y \
6+
curl \
7+
gnupg \
8+
ca-certificates \
9+
lsb-release && \
10+
curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
11+
apt-get install -y nodejs
12+
13+
# Update npm (got installed in the previous layer with `nodejs`)
14+
RUN npm install -g npm@latest
15+
# Copy in our frontend directory. We will need to mount the env.js file externally.
16+
COPY frontend .
17+
18+
19+
# Now building on top of the local middleware image
220
# Precondition: `docker build -t stemmaweb-middleware ./middleware`
3-
FROM stemmaweb-middleware
4-
WORKDIR /usr/src
21+
FROM stemmaweb-middleware AS server
22+
WORKDIR /usr/src/app
523

624
# Copy the static bundle into the container to /usr/src/www
7-
COPY frontend/www www
8-
9-
# Copy the startup script which will handle spawning two processes:
10-
# 1. Plain HTTP server for the static frontend bundle (`python -m http.server`)
11-
# 2. The middleware server (Flask app served by `gunicorn`)
12-
# It also handles generating `env.js`
13-
COPY bin/www-docker.sh .
14-
COPY bin/generate-frontend-env.sh .
25+
COPY --from=frontend /usr/src/frontend/www/ /usr/src/app/stemmaweb_middleware/stemmaweb
1526

16-
# Actually start the above-described script
17-
CMD ./www-docker.sh
27+
# Start gunicorn
28+
EXPOSE ${GUNICORN_PORT}
29+
ENTRYPOINT gunicorn --workers=${GUNICORN_WORKERS} --bind=${GUNICORN_BIND} --log-level=${LOG_LEVEL} --access-logfile=- --error-logfile=- 'stemmaweb_middleware:create_app()'

Makefile

+8-4
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22
build:
33
@echo "==> 🏗 Build Containers"
44
@docker build -t stemmaweb-middleware ./middleware
5-
@docker compose build
5+
@docker compose --env-file .env.prod build
66

77
start: build
88
@echo "==> 🚀 Start"
9-
@docker compose up
9+
@docker compose --env-file .env.prod up
10+
11+
down:
12+
@echo "==> 🛑 Stop Containers"
13+
@docker compose down
1014

1115
# Spawns a new shell in the dev docker container
1216
shell:
@@ -32,7 +36,7 @@ CY_NPM_COMMAND="cy:run"
3236

3337
build-tests:
3438
@echo "==> 🏗 Build Test Containers"
35-
@CY_NPM_COMMAND=$(CY_NPM_COMMAND) docker compose --env-file .env.dev -f docker-compose.test.yml build
39+
@CY_NPM_COMMAND=$(CY_NPM_COMMAND) docker compose --env-file .env.test -f docker-compose.test.yml build
3640

3741
build-tests-arm:
3842
@make build-tests CY_NPM_COMMAND="cy:run:arm"
@@ -43,7 +47,7 @@ tests: tests-down build-tests
4347

4448
tests-down:
4549
@echo "==> 🛑 Stop Test Containers"
46-
@CY_NPM_COMMAND=$(CY_NPM_COMMAND) docker compose --env-file .env.dev -f docker-compose.test.yml down
50+
@CY_NPM_COMMAND=$(CY_NPM_COMMAND) docker compose --env-file .env.test -f docker-compose.test.yml down
4751

4852
tests-arm:
4953
@make tests CY_NPM_COMMAND="cy:run:arm"

bin/generate-frontend-env.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ for ENV_VAR in "${ENV_VARS_TO_COPY[@]}"; do
1515
# Check if the environment variable is set
1616
if [[ -z "${!ENV_VAR}" ]]; then
1717
# If the environment variable is not set, print an error message and exit
18-
echo -e "\e[1;31mERROR: Environment variable $ENV_VAR is not set\e[0m"
18+
echo -e "\e[1;31mERROR: Environment variable $ENV_VAR is not set\e[0m" >&2
1919
exit 1
2020
fi
2121

docker-compose.yml

+7-17
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,15 @@ services:
3535
- stemweb_mysql
3636
- stemweb_redis
3737

38-
stemmarest-initializer:
39-
build: bin
40-
env_file:
41-
- .env.prod
42-
depends_on:
43-
- stemmarest
44-
volumes:
45-
- ./bin/init-data/:/init-data
46-
entrypoint: >
47-
/bin/bash -c "sleep 10 && /init-data/init_test_data.sh"
48-
4938
stemmaweb:
5039
build: .
40+
depends_on:
41+
- stemmarest
42+
- stemweb
5143
env_file:
5244
- .env.prod
53-
54-
reverse-proxy:
55-
build: ./reverse-proxy
56-
depends_on:
57-
- stemmaweb
45+
volumes:
46+
- ./frontend/config/env.js.prod:/usr/src/app/stemmaweb_middleware/stemmaweb/src/js/env.js
5847
ports:
59-
- "8888:80"
48+
- "3000:${GUNICORN_PORT:-3000}"
49+

env.zip.gpg

642 Bytes
Binary file not shown.

frontend/config/env.js.prod

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const STEMMAWEB_MIDDLEWARE_URL = '';
2+
const RECAPTCHA_SITE_KEY = '6LdZPq4ZAAAAAGx6vHrdtUfUr1HryiDoPu4piwiG';

middleware/README.md

+12-11
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,21 @@ required.
1616
| Variable | Description | Default |
1717
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
1818
| `STEMMAREST_ENDPOINT` | The URL where the Stemmarest backend is running | `http://127.0.0.1:8080/stemmarest` |
19-
| `STEMMAWEB_MIDDLEWARE_URL` | The URL where this middleware is running | `http://127.0.0.1:3000` |
20-
| `SECRET_KEY` | Secret key for the Flask application, used by [Flask-Login](https://github.com/maxcountryman/flask-login).<br />Generate it by executing `python -c 'import secrets; print(secrets.token_hex())'` in your shell. | 🚫 |
21-
| `GOOGLE_CLIENT_ID` | Google OAuth client ID | 🚫 |
22-
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret | 🚫 |
23-
| `GITHUB_CLIENT_ID` | GitHub OAuth client identifier | 🚫 |
24-
| `GITHUB_CLIENT_SECRET` | GitHub OAuth client secret | 🚫 |
25-
| `RECAPTCHA_SITE_KEY` | reCAPTCHA public identifier | 🚫 |
26-
| `RECAPTCHA_SECRET_KEY` | reCAPTCHA private key | 🚫 |
27-
| `STEMMAWEB_FRONTEND_URL` | The URL of the frontend. Will be set as the redirect destination after Google OAuth | `http://127.0.0.1:5000` |
19+
| `STEMMAWEB_MIDDLEWARE_URL` | The URL where this middleware is running | `''` |
20+
| `STEMMAWEB_STATIC` | The directory that holds the static files in the front-end (for production mode service) | `static` |
21+
| `SECRET_KEY` | Secret key for the Flask application, used by [Flask-Login](https://github.com/maxcountryman/flask-login).<br />Generate it by executing `python -c 'import secrets; print(secrets.token_hex())'` in your shell. | 🚫 |
22+
| `GOOGLE_CLIENT_ID` | Google OAuth client ID | 🚫 |
23+
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret | 🚫 |
24+
| `GITHUB_CLIENT_ID` | GitHub OAuth client identifier | 🚫 |
25+
| `GITHUB_CLIENT_SECRET` | GitHub OAuth client secret | 🚫 |
26+
| `RECAPTCHA_SITE_KEY` | reCAPTCHA public identifier | 🚫 |
27+
| `RECAPTCHA_SECRET_KEY` | reCAPTCHA private key | 🚫 |
28+
| `STEMMAWEB_FRONTEND_URL` | The URL of the frontend, if the frontend is running separately. Will be set as the redirect destination after Google OAuth | `''` |
2829
| `LOG_LEVEL` | Logging verbosity | `DEBUG` |
2930
| `LOGFILE` | Destination file to store the logs, relative to this module's root | `stemmaweb_middleware.log` |
3031
| `LOG_BACKTRACE` | Whether error backtraces should be logged | `True` |
31-
| `GUNICORN_WORKERS` | Number of workers to be spawned by gunicorn in the production deployment. Passed to `--workers` | `4` |
32-
| `GUNICORN_BIND` | Address to bind the WSGI server to. Passed to `--bind` | `0.0.0.0:3000` |
32+
| `GUNICORN_WORKERS` | Number of workers to be spawned by gunicorn in the production deployment. Passed to `--workers` | 🚫 |
33+
| `GUNICORN_BIND` | Address to bind the WSGI server to. Passed to `--bind` | 🚫 |
3334

3435
### Running with Poetry
3536

middleware/stemmaweb_middleware/app.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from flask import Flask
44
from loguru import logger
55

6-
from . import constants, controller, extensions
6+
from . import constants, controller, extensions, settings
77

88

99
def create_app(config_object="stemmaweb_middleware.settings"):
@@ -13,7 +13,8 @@ def create_app(config_object="stemmaweb_middleware.settings"):
1313
1414
:param config_object: The configuration object to use.
1515
"""
16-
app = Flask(__name__.split(".")[0])
16+
settings.flask_args
17+
app = Flask(__name__.split(".")[0], **settings.flask_args)
1718
app.config.from_object(config_object)
1819
app.secret_key = app.config["SECRET_KEY"]
1920

@@ -81,7 +82,7 @@ def register_blueprints(app: Flask):
8182
)
8283
app.register_blueprint(stemweb_blueprint)
8384

84-
# General health-check endpoint
85+
# General health-check and static service endpoint
8586
health_blueprint = controller.health.routes.blueprint_factory(
8687
stemmarest_client=stemmarest_client, stemweb_client=stemweb_client
8788
)

middleware/stemmaweb_middleware/controller/health/routes.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from flask import Blueprint
1+
from flask import Blueprint, current_app
22

33
from stemmaweb_middleware.resources.base import APIClient
44

@@ -8,7 +8,7 @@ def blueprint_factory(
88
) -> Blueprint:
99
blueprint = Blueprint("health", __name__)
1010

11-
@blueprint.route("/")
11+
@blueprint.route("/health")
1212
def health_check():
1313
return "Middleware is healthy", 200
1414

@@ -22,10 +22,14 @@ def stemmarest_health_check():
2222

2323
@blueprint.route("/stemweb-health")
2424
def stemweb_health_check():
25-
response = stemweb_client.request("GET", "/algorithms/testserver")
25+
response = stemweb_client.request("GET", "/algorithms/available/")
2626
if response.ok:
2727
return "Stemweb is healthy", 200
2828
else:
2929
return "Stemweb is unhealthy", 500
3030

31+
@blueprint.route("/") # Static file service
32+
def stemmaweb_index():
33+
return current_app.send_static_file('index.html')
34+
3135
return blueprint

middleware/stemmaweb_middleware/settings.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
ENV = env.str("FLASK_ENV", default="production")
1616
DEBUG = ENV == "development"
17+
# In case we are serving the frontend from Flask, we set the static folder to some
18+
# reasonable value such as 'stemmaweb' and serve it from the root path.
19+
flask_args = {'static_folder': env.str("STEMMAWEB_STATIC", default=None),
20+
'static_url_path': '/' if env.str('STEMMAWEB_STATIC') else None}
1721

1822
# Used for Stemmarest Client setup
1923
STEMMAREST_ENDPOINT = env.str(
@@ -28,7 +32,7 @@
2832
# Endpoints redirected from this host (`STEMMAWEB_HOST`)
2933
# to the stemmarest server (`STEMMAREST_ENDPOINT`)
3034
STEMMAWEB_MIDDLEWARE_URL = env.str(
31-
"STEMMAWEB_MIDDLEWARE_URL", default="http://127.0.0.1:3000"
35+
"STEMMAWEB_MIDDLEWARE_URL", default=""
3236
)
3337

3438
# Used for Flask-Login
@@ -39,7 +43,7 @@
3943
GOOGLE_CLIENT_ID = env.str("GOOGLE_CLIENT_ID", None)
4044
GOOGLE_CLIENT_SECRET = env.str("GOOGLE_CLIENT_SECRET", None)
4145
STEMMAWEB_FRONTEND_URL = env.str(
42-
"STEMMAWEB_FRONTEND_URL", default="http://127.0.0.1:5000"
46+
"STEMMAWEB_FRONTEND_URL", default=""
4347
)
4448

4549
if not GOOGLE_CLIENT_ID or not GOOGLE_CLIENT_SECRET:

0 commit comments

Comments
 (0)