diff --git a/.github/workflows/build_docker.yml b/.github/workflows/build_docker.yml new file mode 100644 index 000000000..c12d58c58 --- /dev/null +++ b/.github/workflows/build_docker.yml @@ -0,0 +1,40 @@ +name: build docker + +on: + workflow_dispatch: + push: + +jobs: + build-docker: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Set up node version + uses: actions/setup-node@v4 + with: + node-version: '20.9' + - name: Install dependencies + run: yarn install --no-immutable --inline-builds + shell: bash + - name: Webpack build + run: yarn run webpack:prod + shell: bash + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/datavisyn/visyn_core + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./deploy/standalone/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/deploy/standalone/Dockerfile b/deploy/standalone/Dockerfile new file mode 100644 index 000000000..7cc05dee7 --- /dev/null +++ b/deploy/standalone/Dockerfile @@ -0,0 +1,26 @@ +# This Dockerfile is used as standalone container for simple deployments, it will be built automatically by GH Actions in the build.yml +FROM python:3.10-buster + +# Copy everything from our backend to our app folder # need to copy backend because we have to install the python packages +COPY visyn_core/ /app/visyn_core/ +COPY Makefile MANIFEST.in README.md setup.py setup.cfg package.json requirements.txt requirements_dev.txt /app/ + +# define target folder +WORKDIR /app/ + +# Install some build tools and finally python dependencies (numpy is required to build opentsne) +RUN make install + +# Override the setttings.py to use include the bundled frontend +ENV VISYN_CORE__BUNDLES_DIR /app/bundles +# Disable the login and always use a anonymous user +ENV VISYN_CORE__SECURITY__STORE__NO_SECURITY_STORE__ENABLE true +ENV VISYN_CORE__SECURITY__STORE__NO_SECURITY_STORE__USER anonymous + +# copy the pre-built front-end --> comment for development because we mount the volume anyway +COPY bundles/ /app/bundles/ + +# expose default port +EXPOSE 9000 + +CMD ["uvicorn", "visyn_core.server.main:app", "--host", "0.0.0.0", "--port", "9000"] diff --git a/visyn_core/__init__.py b/visyn_core/__init__.py index 0262b9a45..efc5fd608 100644 --- a/visyn_core/__init__.py +++ b/visyn_core/__init__.py @@ -1,6 +1,8 @@ from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles from .plugin.model import AVisynPlugin, RegHelper +from . import manager class VisynPlugin(AVisynPlugin): @@ -21,6 +23,15 @@ def init_app(self, app: FastAPI): app.include_router(create_settings_router()) + @app.on_event("startup") + async def startup(): + # Add the / path at the very end to match all other routes before + bundles_dir = manager.settings.visyn_core.bundles_dir + if bundles_dir: + # Mount the bundles directory as static files to enable the frontend (required in standalone Dockerfile mode) + _log.info(f"Mounting bundles dir: {bundles_dir}") + app.mount("/", StaticFiles(directory=bundles_dir, html=True), name="bundles") + def register(self, registry: RegHelper): # phovea_server registry.append( diff --git a/visyn_core/settings/model.py b/visyn_core/settings/model.py index bba8bca92..165f87db5 100644 --- a/visyn_core/settings/model.py +++ b/visyn_core/settings/model.py @@ -178,6 +178,11 @@ class VisynCoreSettings(BaseModel): ``` """ + bundles_dir: str | None = None + """ + Directory where the bundles are stored. If set, a StaticFiles route at / is added to the application. + """ + disable: DisableSettings = DisableSettings() enabled_plugins: list[str] = []