This guide covers a practical production deployment for the Generative Manim API on Render, Fly.io, Railway, Google Cloud Run, AWS ECS, or any platform that can run a Docker container.
Deploy the API container from the repository root. The API:
- listens on
PORTand defaults to8080 - exposes endpoints such as
/v1/code/generation,/v1/chat/generation,/v1/video/rendering, and/v1/video/generation - runs Manim and FFmpeg inside the container
- stores generated videos either in the container filesystem or Azure Blob Storage, depending on environment variables
For production, Docker is the recommended deployment path because Manim needs system packages such as Cairo, Pango, FFmpeg, and LaTeX.
Use one web service for the API and external object storage for rendered videos.
Client or app
-> Generative Manim API container
-> OpenAI or Anthropic API for code generation
-> Manim plus FFmpeg for rendering
-> Azure Blob Storage or another persistent public storage layer
For small experiments, local container storage is fine. For public or long-running deployments, do not rely on the container filesystem because many cloud platforms use ephemeral disks.
Set these variables in your cloud provider dashboard:
PORT=8080
OPENAI_API_KEY=your_openai_key
ANTHROPIC_API_KEY=your_anthropic_key
GEMINI_API_KEY=your_gemini_key
USE_LOCAL_STORAGE=true
BASE_URL=https://your-api-domain.exampleIf you use Azure Blob Storage for rendered videos, set:
USE_LOCAL_STORAGE=false
AZURE_STORAGE_CONNECTION_STRING=your_azure_storage_connection_string
AZURE_STORAGE_CONTAINER_NAME=your_container_nameBASE_URL should be the public URL of your deployed API when USE_LOCAL_STORAGE=true. This lets the API return usable /public/... video URLs.
- Create a new Web Service in Render.
- Connect your GitHub repository.
- Select Docker as the runtime.
- Use the repository root as the build context.
- Set the environment variables listed above.
- Choose an instance size with enough CPU and memory for Manim renders. Avoid free or very small instances for anything beyond quick smoke tests.
- Deploy.
After deployment, verify the API:
curl https://your-render-service.onrender.com/Expected response:
Generative Manim Processor
Then test code generation:
curl -X POST https://your-render-service.onrender.com/v1/code/generation \
-H "Content-Type: application/json" \
-d '{"prompt":"Create a Manim animation with a blue circle expanding from the center."}'And test rendering:
curl -X POST https://your-render-service.onrender.com/v1/video/rendering \
-H "Content-Type: application/json" \
-d '{
"code": "class DrawCircle(Scene):\n def construct(self):\n circle = Circle(color=BLUE)\n self.play(Create(circle))",
"file_class": "DrawCircle",
"project_name": "cloud-smoke-test",
"iteration": 1,
"aspect_ratio": "16:9",
"stream": false
}'Build the image:
docker build -t generative-manim-api .Run it:
docker run --rm -p 8080:8080 \
-e PORT=8080 \
-e OPENAI_API_KEY="$OPENAI_API_KEY" \
-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
-e GEMINI_API_KEY="$GEMINI_API_KEY" \
-e USE_LOCAL_STORAGE=true \
-e BASE_URL="http://localhost:8080" \
generative-manim-apiOn managed platforms, map the platform's public URL to BASE_URL.
The Dockerfile installs the main native dependencies needed for rendering:
ffmpeglibcairo2-devlibpango1.0-devpkg-config- TeX Live packages for LaTeX and MathTex rendering
- Python dependencies from
api/requirements.txt
If you deploy without Docker, install the same native packages on the host before running:
pip install -r api/requirements.txt
python run.pyDocker is usually simpler because native Manim dependencies vary by operating system.
Video rendering is CPU-heavy and can run for minutes. Start with:
- at least 2 CPU cores for general use
- at least 2 GB memory for simple scenes
- more CPU and memory for LaTeX-heavy, 3D, or long animations
- request timeouts of 5 minutes or more
For production traffic, add a job queue instead of rendering every request inside the web process. A durable design is:
API receives render request
-> enqueue job
-> worker renders video
-> upload video to object storage
-> client polls job status or receives a webhook
That avoids web request timeouts and lets you scale render workers separately from API traffic.
USE_LOCAL_STORAGE=true writes videos into the API's local public directory and returns URLs under /public/.... This is convenient, but it is not durable on many cloud hosts.
USE_LOCAL_STORAGE=false uploads rendered videos through Azure Blob Storage in the current API implementation. If you want S3, Cloudflare R2, or Google Cloud Storage, add a storage adapter with the same behavior as upload_to_azure_storage.
- Render fails with missing LaTeX or Cairo errors: use Docker, or install the native packages from the Dockerfile on the host.
- Returned video URL is localhost: set
BASE_URLto the public API URL. - Videos disappear after redeploy: use external object storage instead of local storage.
- Requests time out: use a larger instance, shorter scenes, or a background worker queue.
- Out-of-memory errors: increase instance memory or reduce render quality/scene complexity.
Generated Manim code is Python code. Treat rendering as untrusted code execution unless all prompts and code come from trusted users. For public deployments, isolate render workers, restrict network access where possible, add authentication, rate-limit requests, and set per-job time and memory limits.