Skip to main content
Container-based example app Check out the container-based example app for understanding the basics of running a containerized application on fal.
import fal
from fal.container import ContainerImage
from fal.toolkit import Image, optimize

from pydantic import BaseModel, Field

dockerfile_str = """
FROM python:3.11

RUN apt-get update && apt-get install -y ffmpeg

RUN python -m venv .venv
ENV PATH="$PWD/.venv/bin:$PATH"
RUN pip install "accelerate" "transformers>=4.30.2" "diffusers>=0.26" "torch>=2.2.0"
"""


class Input(BaseModel):
    prompt: str = Field(
        description="The prompt to generate an image from.",
        examples=[
            "A cinematic shot of a baby racoon wearing an intricate italian priest robe.",
        ],
    )


class Output(BaseModel):
    image: Image = Field(
        description="The generated image.",
    )


class FalModel(fal.App):
    image = ContainerImage.from_dockerfile_str(dockerfile_str)
    machine_type = "GPU"

    def setup(self) -> None:
        import torch
        from diffusers import AutoPipelineForText2Image

        # Load SDXL
        self.pipeline = AutoPipelineForText2Image.from_pretrained(
            "stabilityai/stable-diffusion-xl-base-1.0",
            torch_dtype=torch.float16,
            variant="fp16",
        )
        self.pipeline.to("cuda")

        # Apply fal's spatial optimizer to the pipeline.
        self.pipeline.unet = optimize(self.pipeline.unet)
        self.pipeline.vae = optimize(self.pipeline.vae)

        # Warm up the model.
        self.pipeline(
            prompt="a cat",
            num_inference_steps=30,
        )

    @fal.endpoint("/")
    def text_to_image(self, input: Input) -> Output:
        result = self.pipeline(
            prompt=input.prompt,
            num_inference_steps=30,
        )
        [image] = result.images
        return Output(image=Image.from_pil(image))
Voila! 🎉 The highlighted changes are the only modifications you need to make; the rest remains your familiar fal application.
Dockerfile KeywordsPlease check our Dockerfile best practices for more information on how to optimize your Dockerfile.

fal Specific Considerations

When deploying your application on fal, you don’t need to worry about enabling Docker Buildx or BuildKit. We take care of it for you. However, there are several fal-specific requirements you must follow:

Required Package Versions

fal has specific dependencies that must be installed with exact versions:
  • pydantic==2.10.6
  • protobuf==4.25.1
  • boto3==1.35.74
CRITICAL: These packages must be installed LAST in your Dockerfile to ensure they override any conflicting versions installed by other dependencies.
FROM falai/base:3.11-12.1.0

# Install your application dependencies first
RUN pip install torch transformers your-packages

# ALWAYS install fal packages last to avoid version conflicts
RUN pip install --no-cache-dir \\
    boto3==1.35.74 \\
    protobuf==4.25.1 \\
    pydantic==2.10.6

Ensure Curl

FROM ubuntu:22.04

# Install Curl
RUN apt-get update && apt-get install -y curl

1. Using COPY and ADD Commands

Experimental FeatureCOPY and ADD support for local files is currently an experimental feature. Please report any issues you encounter.
fal supports standard Docker COPY and ADD commands to include local files in your container builds.
Important Notes
  • Deduplication with app_files: Any files you’ve previously uploaded with app_files are already available in the build context. They won’t be re-uploaded—fal uses content-based hashing to deduplicate files.
  • App file changes don’t trigger rebuilds: The file containing your fal.App class is not baked into the container image. Instead, it’s pickled/serialized and sent directly at deploy time. This means changes to your app file won’t trigger a container rebuild—only changes to other files referenced in COPY/ADD commands will.

Basic Usage

Simply use COPY or ADD in your Dockerfile to include local files:
import fal
from fal.container import ContainerImage

dockerfile_str = """
FROM python:3.11

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY src/ ./src/
COPY config/ ./config/
"""

class MyApp(fal.App):
    image = ContainerImage.from_dockerfile_str(dockerfile_str)

    @fal.endpoint("/")
    def endpoint(self):
        from src.main import process
        return process()

Supported Patterns

Simple COPY:
WORKDIR /app
COPY . .
Specific Files/Directories:
COPY requirements.txt .
COPY src/ ./src/
COPY utils/ /app/utils/
Glob Patterns:
COPY *.py /app/
COPY config/*.json /app/config/
Multiple Sources:
COPY file1.py file2.py /app/

Build Context Directory

By default, all COPY/ADD paths are resolved relative to the current working directory (where you run fal deploy or fal run). You can customize this using the context_dir parameter:
from pathlib import Path

# Resolve paths relative to a different directory
image = ContainerImage.from_dockerfile_str(
    dockerfile_str,
    context_dir=Path(__file__).parent / "docker_context"
)

# Or use an absolute path
image = ContainerImage.from_dockerfile_str(
    dockerfile_str,
    context_dir="/path/to/my/project"
)
This is useful for monorepos where each service has its own build context, or when you need to COPY shared code from a parent directory.

.dockerignore Support

Create a .dockerignore file in your project to exclude files from the build context:
# .dockerignore
.git
.venv
venv
__pycache__
*.pyc
.env
node_modules
You can also explicitly define ignore patterns in code:
custom_image = ContainerImage.from_dockerfile_str(dockerfile_str)

# Add ignore patterns
custom_image.add_dockerignore(patterns=[".git", "*.pyc", ".env"])

# Or provide an explicit path
custom_image.add_dockerignore(path="docker/.dockerignore")
If no .dockerignore exists, fal uses sensible defaults that exclude common development artifacts.

Multi-Stage Builds

For complex builds, use multi-stage Dockerfiles. Note that COPY --from=... (multi-stage copies) are correctly handled—only local files are collected:
dockerfile_str = """
# Stage 1: Builder
FROM python:3.11 AS builder
WORKDIR /build
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install -r requirements.txt

# Stage 2: Final
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /opt/venv /opt/venv
COPY src/ ./src/
ENV PATH="/opt/venv/bin:$PATH"
"""

Cache Behavior

Container images are cached based on:
  • Dockerfile content
  • Build secrets
  • Content hash of all context files
Changing any local file (except the app file) or a secret triggers a rebuild. Same files = cache hit.

Alternative: Upload Files to CDN

For very large files or files that change independently, you can still upload them to fal’s CDN:
json_url = File.from_path("my-file.json", repository="cdn").url

dockerfile_str = f"""
FROM python:3.11-slim
RUN apt-get update && apt-get install -y curl
RUN curl '{json_url}' > my-file.json
"""
or you can use ADD to directly download the file from the URL:
json_url = File.from_path("requirements.txt", repository="cdn").url

dockerfile_str = f"""
FROM python:3.11-slim
ADD {json_url} /app/requirements.txt
WORKDIR /app
RUN pip install -r requirements.txt
"""

2. Container Image Best Practices

When building container images for fal, follow these best practices:

Use Fal Base Image

Its recommended to use falai/base:3.11-12.1.0 as your base image as it comes with the right python version, cuda and more. Most importantly its small size improves startup times!

Pin All Package Versions

This ensures reproducibility of builds leaving no doors open for issues with newer package versions and incompatibility!
# Good: Pinned versions ensure reproducible builds
RUN pip install torch==2.6.0 transformers==4.51.3

# Bad: Unpinned versions can break your app
RUN pip install torch transformers

Clean Up Package Caches

Cleaning up package caches reduces build time and startup time, making for a faster iteration and coldstart!
RUN apt-get update && apt-get install -y package \\
    && rm -rf /var/lib/apt/lists/*

RUN pip install --no-cache-dir package==version

Use Multi-Stage Builds for Smaller Images

Multi Stage builds are a great way to significantly reduce the image size, saving time building and downloading the container on startup!
# Build stage
FROM python:3.11 as builder
COPY requirements.txt .
RUN pip install --user -r requirements.txt

# Runtime stage
FROM python:3.11-slim
COPY --from=builder /root/.local /root/.local

Docker Templates

To help you get started quickly and avoid common pitfalls, here are production-ready Docker templates for common use cases:

Base Python Template

Perfect for applications that only need Python packages from pip or simple apt packages.
dockerfile_str = """
FROM falai/base:3.11-12.1.0

USER root

RUN apt-get update && apt-get install -y --no-install-recommends \\
    git \\
    wget \\
    curl \\
    && rm -rf /var/lib/apt/lists/*

# Install your application packages
RUN pip install --no-cache-dir \\
    requests==2.31.0 \\
    numpy==1.24.3 \\
    pandas==2.0.3

# IMPORTANT: Install fal-required packages LAST to ensure correct versions
RUN pip install --no-cache-dir \\
    boto3==1.35.74 \\
    protobuf==4.25.1 \\
    pydantic==2.10.6
"""

class FalModel(fal.App):
    image=ContainerImage.from_dockerfile_str(dockerfile_str),
    # Your application code

PyTorch + HuggingFace Template

For deep learning applications using PyTorch and HuggingFace ecosystem.
FROM falai/base:3.11-12.1.0

# Install PyTorch with CUDA support first
RUN pip install --no-cache-dir \\
    torch==2.6.0 \\
    accelerate==1.6.0 \\
    transformers==4.51.3 \\
    diffusers==0.31.0 \\
    hf_transfer==0.1.9 \\
    peft==0.15.0 \\
    sentencepiece==0.2.0 \\
    --extra-index-url \\
    https://download.pytorch.org/whl/cu124

# IMPORTANT: Install fal-required packages LAST to ensure correct versions
RUN pip install --no-cache-dir \\
    boto3==1.35.74 \\
    protobuf==4.25.1 \\
    pydantic==2.10.6

# Set CUDA environment variables
ENV CUDA_HOME=/usr/local/cuda
ENV PATH=$CUDA_HOME/bin:$PATH
ENV LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH
ENV TORCH_CUDA_ARCH_LIST="6.0 6.1 7.0 7.5 8.0 8.6 8.9 9.0 9.0a"

Custom CUDA Template

For some applications, you might require a different cuda runtime, here is an example to get CUDA 12.8:
FROM nvidia/cuda:12.8.0-runtime-ubuntu22.04

# Avoid prompts during apt install
ENV DEBIAN_FRONTEND=noninteractive \\
    PYTHONDONTWRITEBYTECODE=1 \\
    PYTHONUNBUFFERED=1

# Install Python and system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \\
    software-properties-common \\
    && add-apt-repository ppa:deadsnakes/ppa \\
    && apt-get update && apt-get install -y --no-install-recommends \\
    python3.11 \\
    python3.11-dev \\
    python3.11-venv \\
    python3-pip \\
    wget \\
    curl \\
    ca-certificates \\
    ffmpeg \\
    libsndfile1 \\
    && rm -rf /var/lib/apt/lists/*


# IMPORTANT: Create symlinks for python binary accessibility
# fal requires the python binary to be accessible via standard paths
RUN ln -sf /usr/bin/python3.11 /usr/bin/python3 && \\
    ln -sf /usr/bin/python3.11 /usr/bin/python

# Upgrade pip
RUN python3 -m pip install --no-cache-dir --upgrade pip

# Install PyTorch first (CUDA 12.8 compatible)
RUN pip install torch==2.7.0 -f https://download.pytorch.org/whl/cu128/torch_stable.html

# Install your packages
RUN pip install --no-cache-dir \
    stable-audio-tools==0.0.19 \
    librosa==0.10.1 \
    soundfile==0.12.1

# IMPORTANT: Install fal-required packages LAST to ensure correct versions
RUN pip install --no-cache-dir \\
    boto3==1.35.74 \\
    protobuf==4.25.1 \\
    pydantic==2.10.6

Common Issues & Solutions

fal Dependency Conflicts

Problem: ImportError or version conflicts with pydantic, protobuf, or boto3 Solution: Always install fal-required packages last:
# Install all other packages first
RUN pip install torch transformers your-other-packages

# Install fal-required packages LAST
RUN pip install --no-cache-dir \\
    boto3==1.35.74 \\
    protobuf==4.25.1 \\
    pydantic==2.10.6

Python Binary Not Found

Problem: python: command not found or /usr/bin/env: python: No such file or directory Solution: Create proper symlinks when using custom base images:
RUN ln -sf /usr/bin/python3.11 /usr/bin/python3 && \\
    ln -sf /usr/bin/python3.11 /usr/bin/python
Example Problem: RuntimeError: No CUDA GPUs are available Solution: Ensure CUDA environment variables are set:
ENV CUDA_HOME=/usr/local/cuda
ENV PATH=$CUDA_HOME/bin:$PATH
ENV LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH

Container Validation Checklist

Before deploying your container app, ensure:
  • All package versions are pinned
  • fal-required packages (pydantic==2.10.6, protobuf==4.25.1, boto3==1.35.74) are installed LAST
  • Curl is installed
  • Container builds without errors with fal run

Using Private Docker Registries

To use private docker registries, you need to provide registry credentials like so:

Dockerhub

class FalModel(fal.App):
    image=ContainerImage.from_dockerfile_str(
        "FROM myuser/image:tag",
        registries={
            "https://index.docker.io/v1/": {
                "username": "myuser",
                "password": "$DOCKERHUB_TOKEN",  # use `fal secrets set` first to create this secret
            },
        },
    ),

    ...

Google Artifact Registry

We recommend using a service account and setting a base64-encoded version of the key as a Fal secret, which you can then use in your code:
1

Create a JSON key for a service account. It should be automatically downloaded to your computer.

2

Encode it in base64 with a command like:

cat key.json | base64
3

Set the result as a Fal secret:

fal secrets set GOOGLE_AR_JSON_BASE64=<value from above>
4
Use the secret as the password, and _json_key_base64 as the username for the Artifact Registry in your code:
class FalModel(fal.App):
    image=ContainerImage.from_dockerfile_str(
        "FROM us-central1-docker.pkg.dev/myuser/image:tag",
        registries={
            "us-central1-docker.pkg.dev": {
                "username": "_json_key_base64",
                "password": "$GOOGLE_AR_JSON_BASE64",
            },
        },
    ),
)
    ...
For more details and options check out Google’s documentation.

Amazon Elastic Container Registry

Use aws ecr get-login-password --region us-east-1 to get a token (see docs). Make sure that the token matches the region of your repo, otherwise you will get a 400 error. Note: tokens only last 12 hours, so it’s best to auto-generate one on the fly with boto3 and AWS credentials.
# Optional: automatically generate a new ECR token since they expire every 12 hours
import os
import boto3

def get_ecr_token(region: str) -> str:
    ecr_client = boto3.client(
        'ecr',
        aws_access_key_id=os.environ["AWS_ACCESS_KEY_ID"],
        aws_secret_access_key=os.environ["AWS_SECRET_ACCESS_KEY"],
        region_name=region
    )

    response = ecr_client.get_authorization_token()
    auth_data = response['authorizationData'][0]
    token = auth_data['authorizationToken']

    # Decode from base64, format is "AWS:password"
    decoded_token = base64.b64decode(token).decode('utf-8')
    _, password = decoded_token.split(':', 1)

    return password

ecr_token = get_ecr_token("us-east-1")
# End Optional

class FalModel(fal.App):
    image=ContainerImage.from_dockerfile_str(
        "FROM 123456789012.dkr.ecr.us-east-1.amazonaws.com/image:tag",
        registries={
            "1234567890.dkr.ecr.us-east-1.amazonaws.com": {
                "username": "AWS",
                "password": ecr_token,
            },
        },
    ),

    ...

Build Secrets

We currently only support secret mounts.
class FalModel(fal.App):
    image=ContainerImage.from_dockerfile_str(
        """
        FROM python:3.11
        RUN --mount=type=secret,id=aws-key-id,env=AWS_ACCESS_KEY_ID \
            --mount=type=secret,id=aws-secret-key,env=AWS_SECRET_ACCESS_KEY \
            --mount=type=secret,id=aws-session-token,env=AWS_SESSION_TOKEN \
            aws s3 cp ...
        """,
        secrets={
            # use `fal secrets set` first to create these secrets
            "aws-key-id": "$AWS_ACCESS_KEY_ID",
            "aws-secret-key": "$AWS_SECRET_ACCESS_KEY",
            "aws-session-token": "$AWS_SESSION_TOKEN",
        },
    ),

    ...

Build Args

Use build_args to pass standard Docker build arguments to your Dockerfile. Remember that build args are not secret; avoid putting sensitive values here.
class FalModel(fal.App):
    image=ContainerImage.from_dockerfile_str(
        """
        ARG PY_VERSION=3.11
        FROM python:${PY_VERSION}-slim
        ARG EXTRA_INDEX_URL
        RUN pip install --no-cache-dir --extra-index-url ${EXTRA_INDEX_URL} mypkg
        """,
        build_args={
            "PY_VERSION": "3.11",
            "EXTRA_INDEX_URL": "https://example.com/simple",
        },
    ),

    ...

Disable Build Cache

Container image builds are cached by default to improve build times. However, in some cases you may want to disable the cache and force a rebuild of the container image. You can pass the --force-env-build flag to the fal deploy or fal run command to disable the build cache.
fal deploy --force-env-build path/to/my_app.py::MyApp
fal run --force-env-build path/to/my_app.py::MyApp

Next steps

Optimize your container imageCheck our Optimize Container Images guide for more information on how to optimize your container image.