> ## Documentation Index
> Fetch the complete documentation index at: https://fal.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate an External Docker Server

> Deploy an existing Docker-based HTTP server to fal Serverless.

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.

```mermaid theme={null}
flowchart LR
    Client --> Gateway[fal Gateway]
    Gateway --> Container[Your Container]
    Container --> Server[Server :exposed_port]
```

Your server must bind to `0.0.0.0`, listen on the configured `exposed_port`, and return HTTP `200` from `GET /health`.

```toml theme={null}
[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"
```

```dockerfile theme={null}
FROM your-base-image
# ... your setup

EXPOSE 8000
CMD ["your-server", "--host", "0.0.0.0", "--port", "8000"]
```

Run it:

```bash theme={null}
fal run my-server
```

Deploy it:

```bash theme={null}
fal deploy my-server
```

Your server's API is exposed as-is. If it serves `/generate`, call:

```bash theme={null}
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`:

```bash theme={null}
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"}'
```

<Note>
  The Playground is not supported for Docker container apps. Use direct HTTP calls to `fal.run` or queue-backed calls to `queue.fal.run`.
</Note>

## 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.

```mermaid theme={null}
flowchart LR
    Client --> Gateway[fal Gateway]
    Gateway --> App[fal.App]
    App <--> Server[Internal Server]
    App --> CDN[fal CDN]
```

```python theme={null}
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:

```toml theme={null}
[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:

```bash theme={null}
fal secrets set REGISTRY_TOKEN=<token>
```

## Best Practices

Download model weights to [persistent storage](/documentation/development/use-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](/documentation/deployment/scale-your-application) 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

* [Use Custom Container Images](/documentation/development/use-custom-container-image) - Dockerfile patterns and custom-container choices
* [pyproject.toml Reference](/api-reference/python-sdk/pyproject-toml) - Named app configuration
* [Private Docker Registries](/documentation/development/private-registries) - Registry authentication setup
* [Use Persistent Storage](/documentation/development/use-persistent-storage) - Persistent `/data` storage for models and assets
