Deploy
You can deploy your Mountaineer project using whatever container technology you'd like, on whatever hosting provider you'd like. Most hosts at this point mandate or highly encourage containerization of your dependencies with Docker - so we start there.
This page contains a reasonable default configuration to get you started. We make heavy use of multi-stage builds to cache dependencies and minimize the size of your final image that your webservers will have to pull down.
Docker
First, add the following to your .dockerignore
file. This will prevent Docker from trying to copy over heavy artifacts that aren't needed for the build.
**/node_modules
**/_server
**/_ssr
**/_static
**/_metadata
Image 1: Frontend Dependencies
Our first stage uses npm
to fetch your frontend dependencies. This is an isolated context since it's the only place we need node / npm in the build pipeline.
FROM node:20-slim as node-dependencies
WORKDIR /usr/src/app
# We only require the dependency definitions
COPY {my_webapp}/views/package.json {my_webapp}/views/package-lock.json ./
RUN npm install
Image 2: Python Dependencies Base
Our build pipeline requires Poetry and a basic Python configuration in multiple stages. This initial stage sets up the Poetry CLI and a Python 3.11 environment. By default we make use of Docker's buildx
to compile for linux/amd64 (Intel) since this is what most servers run on. It also lets us leverage our prebuild Mountaineer wheels.
FROM --platform=linux/amd64 python:3.11-slim as poetry-base
WORKDIR /usr/src/app
# You only need `nodejs` here if you are using the postcss plugin
RUN apt-get update \
&& apt-get install -y --no-install-recommends pipx nodejs
ENV PATH="/root/.local/bin:${PATH}"
RUN pipx install poetry
Image 3: Python Dependencies
Fetch Python dependencies and package them into a virtualenv.
FROM poetry-base as venv-dependencies
RUN pipx inject poetry poetry-plugin-bundle
# Only copy package requirements to cache them in docker layer
# We don't copy poetry.lock since this is tied to the specific architecture
# of our dev machines
COPY pyproject.toml ./
# Poetry requires a README.md to be present in the project
COPY README.md ./
# Copy the application code
COPY {my_webapp} ./{my_webapp}
# Gather dependencies and place into a new virtualenv
RUN poetry -vvv bundle venv --python=/usr/local/bin/python --only=main /venv
Image 4: Build Frontend to Javascript
Static frontend plugins, provided by Mountaineer.
FROM poetry-base as server-hooks-builder
COPY pyproject.toml ./
COPY README.md ./
COPY {my_webapp} ./{my_webapp}
COPY --from=node-dependencies /usr/src/app/node_modules ./{my_webapp}/views/node_modules
# Mount the application CLI handlers and build the artifacts
RUN poetry install
RUN poetry run build
Image 5: Final Layer
Combines the raw python files, python dependencies, and the built frontend.
FROM --platform=linux/amd64 python:3.11-slim as final
# Create and switch to a new user
RUN useradd --create-home appuser
USER appuser
ENV PATH="/venv/bin:$PATH"
WORKDIR /usr/src/app
COPY --from=venv-dependencies /venv /venv
COPY --from=server-hooks-builder /usr/src/app/{my_webapp}/views /venv/lib/python3.11/site-packages/{my_webapp}/views
# Run the application
CMD ["/venv/bin/uvicorn", "{my_webapp}.main:app", "--host", "0.0.0.0", "--port", "3000"]
Local Testing
Once your Docker image is built, the best way to test it is to run it locally with docker run
.
However you can also simulate what the production service is doing by running:
poetry run build
ENVIRONMENT=PRODUCTION poetry run uvicorn {my_webapp}.main:app --host localhost --port 5006
This runs with production-minified assets and the same configuration as the production server.
Common Errors
If you see "required file not found" when you try to run this docker image, double check that your venv is pointing to the correct version of Python within the container:
$ ls -ls /venv/bin
4 -rw-r--r-- 1 root root 2209 Mar 29 00:26 activate
4 -rw-r--r-- 1 root root 1476 Mar 29 00:26 activate.csh
4 -rw-r--r-- 1 root root 3039 Mar 29 00:26 activate.fish
4 -rw-r--r-- 1 root root 2724 Mar 29 00:26 activate.nu
4 -rw-r--r-- 1 root root 1650 Mar 29 00:26 activate.ps1
4 -rw-r--r-- 1 root root 1337 Mar 29 00:26 activate_this.py
4 -rwxr-xr-x 1 root root 212 Mar 29 00:27 dotenv
4 -rwxr-xr-x 1 root root 204 Mar 29 00:27 httpx
0 lrwxrwxrwx 1 root root 25 Mar 29 00:26 python -> /usr/local/bin/python3.11
0 lrwxrwxrwx 1 root root 6 Mar 29 00:26 python3 -> python
0 lrwxrwxrwx 1 root root 6 Mar 29 00:26 python3.11 -> python
4 -rwxr-xr-x 1 root root 207 Mar 29 00:27 tqdm
4 -rwxr-xr-x 1 root root 211 Mar 29 00:27 uvicorn
4 -rwxr-xr-x 1 root root 211 Mar 29 00:27 watchfiles
4 -rwxr-xr-x 1 root root 217 Mar 29 00:27 watchmedo
The path python -> /usr/local/bin/python3.11
should be executable and run the interpreter:
$ /venv/bin/python --version
Python 3.11.8