Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Containerize Frontend & Backend #16

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
build
.dockerignore
Dockerfile
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another good candidate to ignore is the .git/ folder, as well as the .gitignore file.

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,5 @@ venv.bak/
# mypy
.mypy_cache/
*.db

docker-compose.yml
125 changes: 125 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
ARG REACT_APP_NAME=frontend
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd be better of splitting this into two dockerfiles and putting them into their respective directories (frontend and backend)

ARG FLASK_APP_NAME=backend
ARG REACT_APP_PATH=/opt/$REACT_APP_NAME
ARG FLASK_APP_PATH=/opt/$FLASK_APP_NAME
ARG PYTHON_VERSION=3.10.0-alpine
ARG POETRY_VERSION=1.1.13
ARG NGINX_VERSION=1.23.0-alpine

########## STAGE 1: STAGING ##########
FROM node:18-alpine AS node-staging
ARG REACT_APP_NAME
ARG REACT_APP_PATH

WORKDIR $REACT_APP_PATH
COPY ./frontend ./

FROM python:3.10 as python-staging

ARG FLASK_APP_NAME
ARG FLASK_APP_PATH

ENV \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONFAULTHANDLER=1
ENV \
POETRY_VERSION=$POETRY_VERSION \
POETRY_HOME="/opt/poetry" \
POETRY_VIRTUALENVS_IN_PROJECT=true \
POETRY_NO_INTERACTION=1

# Install Poetry - respects $POETRY_VERSION & $POETRY_HOME
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python
ENV PATH="$POETRY_HOME/bin:$PATH"

WORKDIR $FLASK_APP_PATH
COPY ./$FLASK_APP_NAME/poetry.lock ./$FLASK_APP_NAME/pyproject.toml ./$FLASK_APP_NAME/tests ./ ./$FLASK_APP_NAME/configmodule.py ./
COPY ./$FLASK_APP_NAME/$FLASK_APP_NAME ./$FLASK_APP_NAME

######### DEV STAGE 2: serve frontend #########
FROM node-staging as frontend-dev
ARG REACT_APP_NAME
ARG REACT_APP_PATH
#ENV NODE_ENV="development"
#ENV HTTP_PROXY="http://host.docker.internal:1337"

WORKDIR $REACT_APP_PATH
RUN npm install
CMD ["npm", "run", "start"]

########## DEV STAGE 3: serve backend #########
FROM python-staging as backend-dev
ARG FLASK_APP_NAME
ARG FLASK_APP_PATH

WORKDIR $FLASK_APP_PATH
RUN poetry install

# CONFIG VARS
# ENV DEVEL=true \
# DB_URI="postgresql://eelbot:[email protected]:5432/eelbot" \
# BACKEND_HOST="0.0.0.0" \
# BACKEND_PORT_DEV='1337'

CMD ["poetry", "run", "server"]

######## PROD STAGE 1: build backend #########
FROM python-staging as python-build-env
ARG FLASK_APP_PATH
ARG FLASK_APP_NAME

WORKDIR $FLASK_APP_PATH
RUN poetry build --format wheel
RUN poetry export --format requirements.txt --output constraints.txt --without-hashes

######## PROD STAGE 2: serve backend #########
FROM python:$PYTHON_VERSION as backend-prod
ARG FLASK_APP_NAME
ARG FLASK_APP_PATH

ENV \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONFAULTHANDLER=1

ENV \
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_DEFAULT_TIMEOUT=100

# CONFIG VARS
# ENV DB_URI="postgresql://eelbot:[email protected]:5432/eelbot" \
# BACKEND_HOST="0.0.0.0" \
# BACKEND_PORT="1338"

WORKDIR $FLASK_APP_PATH
COPY --from=python-build-env $FLASK_APP_PATH/dist/*.whl ./
COPY --from=python-build-env $FLASK_APP_PATH/constraints.txt ./
RUN pip install ./${FLASK_APP_NAME}*.whl --constraint constraints.txt

ENV FLASK_APP_NAME $FLASK_APP_NAME

WORKDIR "/usr/local/lib/python3.10/site-packages/$FLASK_APP_NAME"

CMD python -m $FLASK_APP_NAME

######## PROD STAGE 3: build frontend #########
FROM node-staging as node-build-env
ARG REACT_APP_NAME
ARG REACT_APP_PATH

WORKDIR $REACT_APP_PATH

ENV NODE_ENV="production"

RUN npm install --production
RUN npm run build

######## PROD STAGE 4: serve frontend #########
FROM nginx:$NGINX_VERSION as frontend-prod
ARG REACT_APP_NAME
ARG REACT_APP_PATH

COPY --from=node-build-env $REACT_APP_PATH/build /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]
85 changes: 84 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,87 @@
A web based config tool to configure [Eelbot](https://github.com/Emseers/Eelbot), a discord bot for Emseers.

## Implemenation
Implemented as a RESTful API powered by flask and a React based SPA.
Implemented as a RESTful API powered by flask and a React based SPA.


## Installation & Deployment

### Frontend

#### Setup
1. Navigate into the `frontend` directory

2. install & resolve dependencies by using the following command:
```bash
npm install
```

#### Deployment

3. To run the app in development mode:
```bash
npm run start
```

For production, you may build the app using:
```bash
npm run build
```

Note: edit the config file to add your desired ports, hosts, and database location

### Backend

#### Setup

1. Navigate into the `backend` directory

2. install & resolve dependencies & create the virtual environment using the following command:
```bash
poetry install
```
3. To run the DEV version add the environment variable: `DEVEL = 'true'`, otherwise the production server will run.

#### Deployment

To run the server within the virtual environment:

```bash
`poetry run server`
```

## Docker

As an alternative to building and running eelbot-ui locally, you can build and run it in Docker. Build the images with the provided Dockerfile:

### Development server Images
```bash
docker build --target frontend-dev -t frontend-dev:latest .
docker build --target backend-dev -t backend-dev:latest .
```

### Production server Images
```bash
docker build --target frontend-prod -t frontend:latest .
docker build --target backend-prod -t backend:latest .
```

You need to mount all required files and folders to run the containers:

## Run Development Containers
```bash
docker run -it --name frontend -p <PORT:PORT> frontend-dev:latest
```

```bash
docker run -it --name backend -p <PORT:PORT> -v <full/path/to/configmodule.py>:/opt/backend/configmodule.py backend-dev:latest
```

## Run Production Containers
```bash
docker run -it --name backend -p <PORT:PORT> -v <full/path/to/configmodule.py>:/usr/local/lib/python3.10/site-packages/backend/configmodule.py backend:latest
```

```bash
docker run -it --name frontend -p <PORT:PORT> -v <full/path/to/nginx.conf>:/etc/nginx/conf.d/default.conf frontend:latest
```
3 changes: 3 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.dockerignore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this line needed? I always assumed .dockerignore would be automatically ignored, but correct me if I'm wrong.

Dockerfile
Dockerfile.prod
6 changes: 0 additions & 6 deletions backend/README.md

This file was deleted.

7 changes: 0 additions & 7 deletions backend/backend/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +0,0 @@
import os
__version__ = '0.1.0'

def main():
from backend.app import DevApp, ProdApp
app = DevApp() if 'DEVEL' in os.environ else ProdApp()
app.serve()
10 changes: 10 additions & 0 deletions backend/backend/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import os
__version__ = '0.1.0'

def main():
from backend.app import DevApp, ProdApp
app = DevApp() if 'DEVEL' in os.environ else ProdApp()
app.serve()

if __name__ == "__main__":
main()
17 changes: 9 additions & 8 deletions backend/backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
from abc import abstractmethod
from backend.utils.eeljokes import db
from backend.endpoints import jokes_bp

from configmodule import Config, DevelopmentConfig, ProductionConfig

import waitress

class App:
app = None

def __init__(self):
self.app = Flask(__name__)
self.app.config['SQLALCHEMY_DATABASE_URI'] = "postgresql://eelbot:mysecretpassword@localhost:5432/eelbot" # TODO: Use environment variables instead
db.init_app(self.app)

@abstractmethod
def configure_app(self):
Expand All @@ -29,26 +30,26 @@ def __init__(self):
super().__init__()
self.configure_app()
self.register_app()
db.init_app(self.app)

def configure_app(self):
super().configure_app()
self.host = "0.0.0.0" # TODO: Replace with reading from config
self.port = "1337" # TODO: Replace with reading from config
self.app.config.from_object('configmodule.DevelopmentConfig')

def serve(self):
self.app.run(host=self.host, port=self.port)
self.app.run(host=self.app.config['HOST'], port=self.app.config['PORT'])

class ProdApp(App):

def __init__(self):
super().__init__()
self.configure_app()
self.register_app()
db.init_app(self.app)

def configure_app(self):
super().configure_app()
self.host = "0.0.0.0" # TODO: Replace with reading from config
self.port = "1338" # TODO: Replace with reading from config
self.app.config.from_object('configmodule.ProductionConfig')

def serve(self):
waitress.serve(self.app, host=self.host, port=self.port)
waitress.serve(self.app, host=self.app.config['HOST'], port=self.app.config['PORT'])
13 changes: 13 additions & 0 deletions backend/configmodule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import os
basedir = os.path.abspath(os.path.dirname(__file__))

class Config:
SQLALCHEMY_DATABASE_URI = os.environ.get('DB_URI') or "postgresql://eelbot:[email protected]:5432/eelbot"
HOST = os.environ.get('BACKEND_HOST') or "127.0.0.1"

class DevelopmentConfig(Config):
ENV = 'development'
PORT = os.environ.get('BACKEND_PORT_DEV') or "1337"

class ProductionConfig(Config):
PORT = os.environ.get('BACKEND_PORT') or "1338"
11 changes: 10 additions & 1 deletion backend/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ Flask = "2.0.0"
Flask-SQLAlchemy = "2.5.0"
waitress = "2.1.1"
Flask-Cors = "3.0.9"
psycopg2-binary = "^2.9.3"

[tool.poetry.dev-dependencies]
pytest = "^5.2"

[tool.poetry.scripts]
server = "backend:main"
server = "backend.__main__:main"

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
Loading