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.
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:
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:
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:
Create a JSON key for a service account. It should be automatically downloaded to your computer.
Encode it in base64 with a command like:
Set the result as a Fal secret:
fal secrets set GOOGLE_AR_JSON_BASE64=<value from above>
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.