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 uv and a basic Python configuration in multiple stages. This initial stage sets up the uv 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 prebuilt Mountaineer wheels.
FROM --platform=linux/amd64 python:3.11-slim as uv-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 nodejs
RUN pip install uv
ENV UV_LINK_MODE=copy
Image 3: Python Dependencies
Fetch Python dependencies and package them into a virtualenv.
FROM uv-base as venv-dependencies
ENV UV_PROJECT_ENVIRONMENT=/venv
# Only copy package requirements to cache them in the docker layer
COPY pyproject.toml uv.lock ./
# The local package install still needs the project README
COPY README.md ./
# Copy the application code so the local package can be installed
COPY {my_webapp} ./{my_webapp}
# Gather runtime dependencies and place them into a new virtualenv
RUN uv sync --locked --no-dev
Image 4: Build Frontend to Javascript
Static frontend plugins, provided by Mountaineer.
FROM uv-base as server-hooks-builder
COPY pyproject.toml uv.lock ./
COPY README.md ./
COPY {my_webapp} ./{my_webapp}
COPY --from=node-dependencies /usr/src/app/node_modules ./{my_webapp}/views/node_modules
# Install the project and build the frontend artifacts
RUN uv sync --locked
RUN uv 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:
uv run build
ENVIRONMENT=PRODUCTION uv 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