Skip to main content
If you already have a Docker container that runs an HTTP server, you can move it to fal with minimal changes. There are two common paths:
  • Direct Server Mode: Deploy the container directly with pyproject.toml.
  • Proxy App Mode: Run the server inside a custom image and wrap it with fal.App.
Use Direct Server Mode when your existing server API can be exposed as-is. Use Proxy App Mode when you want a typed Python API, Pydantic validation, fal CDN helpers, or Playground support.

Option 1: Direct Server Mode

Direct Server Mode routes incoming traffic straight to your container’s HTTP port. Your server must bind to 0.0.0.0, listen on the configured exposed_port, and return HTTP 200 from GET /health.
[tool.fal.apps.my-server]
auth = "private"
machine_type = "GPU-A100"
exposed_port = 8000
keep_alive = 300

[tool.fal.apps.my-server.image]
dockerfile = "Dockerfile"
FROM your-base-image
# ... your setup

EXPOSE 8000
CMD ["your-server", "--host", "0.0.0.0", "--port", "8000"]
Run it:
fal run my-server
Deploy it:
fal deploy my-server
Your server’s API is exposed as-is. If it serves /generate, call:
curl -H "Authorization: Key $FAL_KEY" \
  https://fal.run/<your-username>/my-server/generate
For queue-backed requests, submit to the same endpoint ID on queue.fal.run:
curl -X POST https://queue.fal.run/<your-username>/my-server/generate \
  -H "Authorization: Key $FAL_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "a mountain at sunrise"}'
The Playground is not supported for Docker container apps. Use direct HTTP calls to fal.run or queue-backed calls to queue.fal.run.

Option 2: Proxy App Mode

Use fal.App to wrap your server with custom endpoints. The internal server runs inside the same container, while the public API is defined by Python methods.
import subprocess
import time

import fal
import requests
from fal.container import ContainerImage
from fal.toolkit import Image
from fastapi import Request
from pydantic import BaseModel, Field

DOCKERFILE = """
FROM your-base-image
# ... your setup
RUN pip install --no-cache-dir fal requests
"""

SERVER_PORT = 8000

class GenerateRequest(BaseModel):
    prompt: str = Field(description="Text prompt")

class GenerateResponse(BaseModel):
    image: Image

class MyServerProxy(fal.App, keep_alive=300, max_concurrency=1):
    machine_type = "GPU-A100"
    image = ContainerImage.from_dockerfile_str(DOCKERFILE)

    def setup(self):
        self.process = subprocess.Popen(
            ["your-server", "--host", "127.0.0.1", "--port", str(SERVER_PORT)],
        )
        self._wait_for_server()

    def _wait_for_server(self, timeout=120):
        start = time.time()
        while time.time() - start < timeout:
            try:
                if requests.get(f"http://127.0.0.1:{SERVER_PORT}/", timeout=5).ok:
                    return
            except requests.ConnectionError:
                pass
            time.sleep(1)
        raise TimeoutError("Server did not start")

    @fal.endpoint("/generate")
    def generate(self, input: GenerateRequest, request: Request) -> GenerateResponse:
        resp = requests.post(
            f"http://127.0.0.1:{SERVER_PORT}/api/generate",
            json={"prompt": input.prompt},
            timeout=300,
        )
        resp.raise_for_status()

        image = Image.from_path(resp.json()["path"], request=request)
        return GenerateResponse(image=image)
Your fal.App controls the API. You can validate inputs, process outputs, upload files to the fal CDN, and use the Playground generated from your Pydantic schemas.

Using a Private Registry

If your Dockerfile pulls from a private base image, configure registry credentials in the image table:
[tool.fal.apps.my-server.image]
dockerfile = "Dockerfile"

[tool.fal.apps.my-server.image.registries."registry.example.com"]
username = "my-user"
password = "$REGISTRY_TOKEN"
Create the secret before deploying:
fal secrets set REGISTRY_TOKEN=<token>

Best Practices

Download model weights to persistent storage during startup rather than baking them into the Docker image. This keeps the image smaller and lets weights persist across runner restarts. For Python proxy apps, install fal-specific packages such as fal, pydantic, protobuf, and boto3 at the end of the Dockerfile to avoid dependency conflicts. Direct Docker container apps that do not import fal do not need those packages. Set keep_alive based on startup cost and traffic. A longer value avoids repeated cold starts for heavy models; a shorter value reduces idle billing for fast-starting servers.

Next Steps