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 Builder

This stage installs uv, resolves your Python dependencies into a project virtual environment, and builds the frontend artifacts. Copying pyproject.toml, uv.lock, and README.md before the app source lets Docker cache dependency resolution separately from application code changes.

FROM --platform=linux/amd64 python:3.14-slim-bookworm AS builder

WORKDIR /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 curl \
    && rm -rf /var/lib/apt/lists/*

COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

RUN uv venv /app/.venv
ENV VIRTUAL_ENV=/app/.venv
ENV PATH="/app/.venv/bin:${PATH}"
ENV UV_LINK_MODE=copy

# Copy dependency definitions first for better layer caching
COPY pyproject.toml uv.lock README.md ./

RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-install-project

# Copy the application code
COPY {my_webapp} ./{my_webapp}
COPY --from=node-dependencies /usr/src/app/node_modules ./{my_webapp}/views/node_modules

# Build the frontend/script files
RUN --mount=type=cache,target=/root/.cache/uv \
    uv run build

Image 3: Final Layer

Combines the raw Python files, virtual environment, and the built frontend.

FROM --platform=linux/amd64 python:3.14-slim-bookworm AS final

# Create and switch to a new user
RUN useradd --create-home appuser
WORKDIR /app

COPY --from=builder --chown=appuser:appuser /app/.venv /app/.venv
COPY --from=builder --chown=appuser:appuser /app/{my_webapp} ./{my_webapp}

USER appuser

ENV VIRTUAL_ENV=/app/.venv
ENV PATH="/app/.venv/bin:${PATH}"

# Run the application
CMD ["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.14
0 lrwxrwxrwx 1 root root    6 Mar 29 00:26 python3 -> python
0 lrwxrwxrwx 1 root root    6 Mar 29 00:26 python3.14 -> 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.14 should be executable and run the interpreter:

$ /venv/bin/python --version

Python 3.14.3