# Docker

- Tool: Bubble
- Difficulty: Intermediate
- Time required: 90–120 minutes
- Last updated: July 2026

## TL;DR

Connect Bubble to Docker by deploying a Dockerized microservice to a cloud platform (Railway, Render, Fly.io) and then calling its public HTTPS endpoint from Bubble's API Connector. Bubble itself cannot run inside a Docker container — Bubble is a hosted SaaS platform. The integration pattern is always: Docker container provides a REST API, Bubble's API Connector calls that API server-side with secrets stored in Private headers.

## Bubble + Docker: the right mental model

The most important thing to understand before starting: you cannot run Bubble inside a Docker container, and you cannot Dockerize a Bubble app. Bubble is a hosted SaaS platform — your Bubble app lives on Bubble's cloud infrastructure, managed by Bubble.io, and is accessed through a browser. There are no code files to containerize.

The legitimate and highly valuable Bubble + Docker integration works in the opposite direction: your Docker container runs an external service, and Bubble's API Connector calls it.

Here is the practical use case that founders build most often: you have a task that Bubble's no-code environment cannot handle natively — maybe it is a complex data transformation, a machine learning model inference call, a PDF generation service, an image processing pipeline, or a business logic calculation that would be cumbersome in Bubble Workflows. You build that logic as a small REST API in Node.js, Python (FastAPI/Flask), or Go, containerize it with Docker, and deploy the container to a cloud platform that gives it a public HTTPS URL. Then your Bubble app calls that URL from its API Connector whenever a user action needs that processing power.

This pattern is powerful because it gives Bubble access to ANY programming capability — if you can write code for it and expose it as an HTTP endpoint, Bubble can call it. The Docker container handles the heavy lifting; Bubble handles the user interface and workflow orchestration.

The two constraints to understand going in: first, the Docker container must be deployed to a cloud platform with a public HTTPS URL — it cannot run on localhost or inside a private network. Bubble's servers cannot reach private infrastructure. Second, Bubble API Connector calls time out after 30 seconds — if your container needs longer to process a request, you need to implement an async job pattern (return a job ID immediately, let Bubble poll for the result).

## Before you start

- A Bubble app on any plan for API Connector calls to a Docker service; Bubble Starter paid plan or above if you want Backend Workflow scheduling for health polling
- A deployed Docker container with a public HTTPS endpoint (Railway, Render, Fly.io, AWS ECS, Google Cloud Run, or similar cloud platform) — a container running only on localhost will not work with Bubble
- The REST API your Docker container exposes — at minimum a GET /health endpoint and the primary business logic endpoints
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → search 'API Connector' by Bubble)

## Step-by-step guide

### 1. Build and containerize your microservice with a Dockerfile

Before connecting anything to Bubble, your Docker container needs to exist and expose a REST API. If you already have a running container deployed to a cloud platform with a public URL, skip to Step 3. If you are building from scratch, here is the minimal viable setup.

For a Python FastAPI microservice, create a directory with these files:

A 'main.py' with at least a health check endpoint and your business logic endpoint:

```python
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class ProcessRequest(BaseModel):
    data: str

@app.get("/health")
def health():
    return {"status": "ok", "service": "my-bubble-service"}

@app.post("/process")
def process(request: ProcessRequest):
    # Your business logic here
    result = request.data.upper()  # Example: return uppercased text
    return {"result": result, "length": len(request.data)}
```

A 'requirements.txt' with: fastapi and uvicorn[standard]

A 'Dockerfile':

```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
```

The `--host 0.0.0.0` is critical — binding to 0.0.0.0 makes the server accept connections from outside the container. Binding to 127.0.0.1 or localhost would make the container unreachable.

Test locally with Docker to confirm it works before deploying to a cloud platform.

```
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
```

**Expected result:** You have a Dockerfile and a simple REST API that starts correctly with 'docker build -t my-service .' and 'docker run -p 8000:8000 my-service', with GET /health returning {"status": "ok"}.

### 2. Deploy the Docker container to a cloud platform with a public HTTPS URL

Bubble's API Connector requires a public HTTPS URL with a valid TLS certificate. A container running on your local machine is unreachable from Bubble's cloud servers. You must deploy to a cloud platform.

Railway (railway.app) is the fastest path for developers who want to deploy a Docker container in minutes:
1. Create a Railway account and click 'New Project'
2. Choose 'Deploy from GitHub repo' or 'Empty project → Add a service → GitHub repo'
3. Railway auto-detects your Dockerfile and builds the container
4. Go to your service → Settings → Networking → Generate Domain — Railway assigns you a subdomain with automatic HTTPS and a valid TLS certificate
5. Note the URL: something like 'my-service-production.up.railway.app'

Render (render.com) is another option:
1. Create a new Web Service → connect your GitHub repo or paste a Docker image URL
2. Render builds and deploys the container automatically
3. Your service gets a URL like 'my-service.onrender.com' with HTTPS

For Node.js/Express services, the same pattern applies — the cloud platform wraps your container in HTTPS automatically.

IMPORTANT security note: never expose the Docker Engine API (the socket at /var/run/docker.sock or TCP port 2375) publicly. Bubble's API Connector should never call the Docker Engine API on a publicly accessible endpoint — that gives remote code execution access to your entire host. Only expose your own application's REST API endpoints.

After deployment, verify the URL works: open `https://your-service-url/health` in a browser. You should see your health check JSON response.

```
# Railway CLI deployment (alternative to UI)
npm install -g @railway/cli
railway login
railway init
railway up
# Your app will be deployed and Railway prints the public URL
```

**Expected result:** Your Docker container is deployed and reachable at 'https://your-service-url/health' returning {"status": "ok"} with HTTP 200 and a valid HTTPS certificate.

### 3. Add your Docker service URL to the Bubble API Connector

In your Bubble editor, click the Plugins tab in the left sidebar. Click 'Add plugins', search for 'API Connector' (by Bubble), and install it. Click 'API Connector' in your Plugins list.

Click 'Add another API' and name it based on your service (e.g., 'My Processing Service' or 'PDF Generator'). In the Shared headers section, add any authentication headers your Docker service requires. For example, if your service requires an API key header:

Key: `X-API-Key`, Value: your API key, check the 'Private' checkbox. The Private flag ensures this key stays on Bubble's server and is never sent to the browser.

If your Docker service requires Bearer token authentication:
Key: `Authorization`, Value: `Bearer your-service-token`, check the 'Private' checkbox.

If your service is publicly accessible without auth (internal tool with no sensitive data), you can skip the auth header.

Now click 'Add another call'. Name it 'Health Check'. Set type to GET. Enter your URL: `https://your-service-url/health`. Set 'Use as' to 'Data'. Click 'Initialize call', then 'Send'. If you see your health check JSON response (e.g., `{"status": "ok", "service": "my-bubble-service"}`), Bubble will detect the fields — status (text) and service (text).

A successful initialize confirms three things: (1) Bubble's servers can reach your Docker container's public URL, (2) the HTTPS certificate is valid and trusted, and (3) your auth headers are correct.

If the initialize shows 'There was an issue setting up your call', check that the URL starts with https:// (not http://), that the service is running, and that no firewall blocks Bubble's cloud IP ranges from reaching your deployment.

```
{
  "method": "GET",
  "url": "https://your-service-url/health",
  "headers": {
    "X-API-Key": "<your_api_key - Private>"
  }
}
```

**Expected result:** The Initialize call succeeds with HTTP 200 and the health check response fields are detected in Bubble. The API group shows in the Plugins panel ready for additional calls.

### 4. Create the primary business logic API call and bind it to Bubble Workflows

With the health check confirmed, now add the API call for your primary business logic endpoint. Click 'Add another call' in your Docker service API group.

For a POST endpoint (most processing services use POST): Name the call 'Process Data' (or whatever matches your endpoint). Set type to POST. Enter the full URL: `https://your-service-url/process`. Set 'Use as' to 'Action' (since this triggers processing, not loads data for display).

Set the body type to 'JSON body'. Add the dynamic fields your endpoint expects:

```json
{
  "data": "<input_data>",
  "user_id": "<user_id>",
  "options": {
    "format": "<output_format>"
  }
}
```

Replace field names with what your API actually expects. The angled bracket fields like `<input_data>` become dynamic parameters Bubble will let you fill in at runtime.

Click 'Initialize call'. Fill in test values for each dynamic parameter and click 'Send'. Your Docker service should process the test input and return a response. Bubble detects the response fields — for example, `result` (text), `processing_time_ms` (number), `pdf_url` (text).

Now connect it to a Bubble Workflow. Go to the Workflow editor. Add or open a Workflow on a button click. Add a step: click 'Click here to add an action' → Plugins → 'Your Service Name - Process Data'. Map each parameter to Bubble dynamic values:
- input_data: Input Element's value (or the Current User's data field)
- user_id: Current User's unique ID
- output_format: a hardcoded value or a Dropdown's value

RapidDev's team has built Bubble apps with Dockerized ML models, PDF generators, and custom business logic APIs — for complex microservice architectures, book a free scoping call at rapidevelopers.com/contact.

After the action step, add a conditional next step: 'Only when step 1 returned a result' — display the result in a Text element or save it to the Bubble database.

```
{
  "method": "POST",
  "url": "https://your-service-url/process",
  "headers": {
    "X-API-Key": "<your_api_key - Private>",
    "Content-Type": "application/json"
  },
  "body": {
    "data": "<input_data>",
    "user_id": "<user_id>",
    "options": {
      "format": "<output_format>"
    }
  }
}
```

**Expected result:** The Process Data action appears in Bubble's Workflow action panel. Clicking the submit button in preview triggers the Docker service and displays or saves the returned result.

### 5. Set up error handling and handle Bubble's 30-second timeout constraint

Production-grade Bubble + Docker integrations require explicit error handling for several failure modes: container cold starts, processing timeouts, and service errors.

In every Workflow that calls your Docker service, add error handling steps:
1. After the API Connector action step, add a conditional action: 'Only when Step 1 has an error' (Bubble Workflow conditional). Show a user-facing error message: 'Processing failed — please try again in a moment.'
2. In your Docker container's FastAPI/Express code, always return structured error responses with HTTP status codes (400 for bad input, 500 for server errors) so Bubble can distinguish the failure type.

The 30-second timeout is Bubble's hard limit for any Workflow step that calls an external API. If your Docker container's processing takes longer:

Async job pattern implementation:
1. Add a POST /start-job endpoint that validates input, queues the job, and immediately returns `{"job_id": "uuid-here", "status": "queued"}`
2. Add a GET /job-status/{job_id} endpoint that returns the current status and result when complete
3. In Bubble: create a 'Job' data type with fields: job_id (Text), status (Text), result (Text), started_at (Date)
4. Step 1 Workflow: call POST /start-job → create a Job record with the returned job_id
5. Bubble's 'Schedule API Workflow' (paid plan required): set a recurring check every 10 seconds that calls GET /job-status/{job_id} for pending jobs
6. When the status returns 'complete', update the Job record and notify the user

For health monitoring: on paid Bubble plans, you can create a Backend Workflow set to run on a recurring schedule — for example, every 5 minutes calling GET /health on your Docker service and creating a HealthLog record in Bubble's database if the response is not {"status": "ok"}.

```
# Python FastAPI async job pattern
from fastapi import FastAPI, BackgroundTasks
import uuid

app = FastAPI()
jobs = {}  # In production, use Redis or a database

@app.post("/start-job")
async def start_job(request: ProcessRequest, background_tasks: BackgroundTasks):
    job_id = str(uuid.uuid4())
    jobs[job_id] = {"status": "queued", "result": None}
    background_tasks.add_task(run_long_process, job_id, request.data)
    return {"job_id": job_id, "status": "queued"}

@app.get("/job-status/{job_id}")
async def job_status(job_id: str):
    if job_id not in jobs:
        return {"status": "not_found"}
    return {"job_id": job_id, **jobs[job_id]}

async def run_long_process(job_id: str, data: str):
    # Long-running work here
    import asyncio
    await asyncio.sleep(45)  # Simulates 45 seconds of processing
    jobs[job_id] = {"status": "complete", "result": data.upper()}
```

**Expected result:** API call errors show user-friendly messages in the Bubble UI. Long-running processes use the async job pattern with polling. The health monitoring Backend Workflow (if configured) logs service status on a schedule.

### 6. Secure your Docker service and configure Bubble Privacy Rules

Your Docker microservice is now callable from Bubble, but before going live, review security at both the Docker service level and the Bubble data layer.

At the Docker service level:
- Add API key authentication to all endpoints. The API key should be passed in a header (e.g., X-API-Key) and your service should return 401 if it is missing or invalid. Store the expected API key as an environment variable in your cloud platform (Railway: Variables tab; Render: Environment tab) — never hardcode it in the Dockerfile or source code.
- Set CORS headers on your Docker service to restrict origins. Since Bubble's API Connector calls from Bubble's servers (not a browser), CORS is not technically required for the API Connector to work — but it is good practice if your service might ever be called from a browser context.
- Ensure your service validates and sanitizes all input from Bubble before using it in database queries, shell commands, or ML model inference — treat Bubble's API calls as untrusted external input.

At the Bubble level:
- Go to Data tab → Privacy. For any Bubble data types that store results from your Docker service (e.g., ProcessingResult, PDFReport), set Privacy Rules to restrict which fields are visible to which users. At minimum: 'Find this in searches: When Current User's unique ID = Creator's unique ID' for user-specific results.
- Ensure your Bubble app's logged-in state is checked before allowing users to trigger the Docker API call — use a Workflow condition 'Only when Current User is logged in' as a prerequisite step, or use Bubble's page access controls to require login before the page loads.
- If the Docker service result includes sensitive data (financial reports, personal health data, etc.), consider storing only a reference (job_id, download_url) in Bubble's database and having the Docker service enforce access control on the result directly.

```
# Railway/Render environment variable setup
# Set these in your cloud platform's Variables/Environment settings:
# API_KEY=your-long-random-api-key-here
# DATABASE_URL=your-db-url-if-needed

# In your FastAPI app, validate the key:
from fastapi import Header, HTTPException
import os

API_KEY = os.getenv("API_KEY")

def verify_api_key(x_api_key: str = Header(None)):
    if x_api_key != API_KEY:
        raise HTTPException(status_code=401, detail="Invalid API key")
    return x_api_key
```

**Expected result:** Docker service validates API keys and returns 401 for invalid requests. Bubble Privacy Rules restrict data access to the correct user. The integration is secure for production use.

## Best practices

- Always deploy Docker containers to a cloud platform with automatic HTTPS (Railway, Render, Fly.io, AWS ECS with ALB) before connecting to Bubble's API Connector — Bubble requires HTTPS with a valid CA-issued certificate and will reject HTTP or self-signed certificate endpoints.
- Add API key authentication to every endpoint of your Docker service, even if the service is 'internal' — a publicly reachable HTTPS endpoint without auth is accessible to anyone on the internet, not just your Bubble app.
- Store Docker service API keys as Private headers in Bubble's API Connector — never put them in the URL as query parameters where they would appear in Bubble's Logs tab and server access logs.
- Implement a health check endpoint (GET /health) in every Docker microservice and test it from Bubble's API Connector before building the full workflow — a passing health check confirms connectivity, HTTPS, and auth header correctness before you invest time in the business logic integration.
- Set Bubble Privacy Rules on any data types that store Docker service results — go to Data tab → Privacy and restrict read/write access to the data's owner or appropriate user role, not 'All users'.
- Design Docker service endpoints to respond within 25 seconds to safely stay under Bubble's 30-second workflow timeout — use async job queuing for any computation that might exceed this limit.
- Monitor WU consumption for Bubble Workflows that call Docker services — each API Connector call consumes Workload Units proportional to call frequency and response size. If the Bubble app polls the Docker service frequently, consider caching results in the Bubble database and polling less frequently.
- Never expose the Docker Engine API (Docker daemon socket or TCP port 2375) on a publicly accessible endpoint — only expose your application-level REST endpoints. The Docker Engine API provides host-level control and must remain on private networks only.

## Use cases

### Call a Dockerized ML model or data processor from a Bubble app

Deploy a Python FastAPI container running a machine learning model (sentiment analysis, document classification, recommendation engine) to Railway or Fly.io. Bubble sends user-generated content to the container's POST endpoint and receives a prediction or processed result, which is then displayed in the Bubble UI or stored in the database.

Prompt example:

```
When a user submits their feedback text in the Input field and clicks 'Analyze', call the POST /analyze endpoint on the deployed FastAPI container with the body {"text": Input Feedback's value}, then display the returned sentiment_score and label in the Result Text element.
```

### Generate PDFs or reports from Bubble data using a Docker service

Build a Node.js PDF generation service (using Puppeteer or PDFKit) packaged in a Docker container and deployed to Render. Bubble sends report data as a POST request body and receives a signed URL to the generated PDF file, which is stored in Bubble's database and shown to the user for download.

Prompt example:

```
When the 'Generate Report' button is clicked, call the POST /generate-pdf endpoint with a JSON body containing Current User's company name, the report date, and a list of all Invoice records from the database as a JSON array. Display the returned pdf_url as a link the user can click to download.
```

### Build a Docker Hub admin panel inside Bubble

Use the Docker Hub REST API to build an internal container registry management dashboard in Bubble — displaying repositories, tag lists, and pull counts. Authenticate with Docker Hub's API using a server-side PAT and list repos from your organization.

Prompt example:

```
On page load, call GET https://hub.docker.com/v2/repositories/yourorgname/?page_size=25 with the Authorization Bearer token in a Private header, and display each repository in a Repeating Group showing the name, description, pull_count, and last_updated date.
```

## Troubleshooting

### Bubble API Connector Initialize call shows 'There was an issue setting up your call' when calling the Docker service URL

Cause: The Docker container's URL is either HTTP (not HTTPS), uses a self-signed certificate, the container is not running, or the cloud platform's URL is incorrect. Bubble's API Connector requires a valid public HTTPS endpoint.

Solution: Open the service URL (https://your-service-url/health) directly in a browser tab. If it loads, the URL and HTTPS certificate are correct — check Bubble's API Connector configuration for typos. If it shows a browser certificate error, the TLS certificate is not from a trusted CA; use a cloud platform that manages HTTPS automatically (Railway, Render, Fly.io). If the page does not load at all, the container may be stopped — check your cloud platform dashboard.

### Docker service calls succeed in testing but time out in production Bubble Workflows

Cause: Bubble Workflows have a hard 30-second timeout for any API Connector action. If your Docker container performs heavy computation (ML inference, large PDF generation, data processing), it may exceed this limit under production load or when cold-starting on a free cloud tier.

Solution: Implement the async job pattern: modify your Docker service to immediately return a job_id on POST and process the work in a background task. Add a GET /job-status/{job_id} endpoint. Use Bubble's Schedule API Workflow (paid plan) to poll the status endpoint every 10 seconds until the job is complete. Free tier services on Render also cold-start after inactivity — upgrade to a paid tier for consistent response times.

### Docker Hub API calls return 401 Unauthorized even with valid credentials

Cause: Docker Hub's REST API requires a two-step authentication: you must first POST to /v2/users/login to obtain a JWT token, then use that token as Bearer auth on subsequent calls. Bubble API Connector does not natively support this multi-step auth flow.

Solution: Create two separate API calls in Bubble: Call 1 (Action) = POST /v2/users/login with username and password in the body, capture the returned token. Call 2 uses the token returned from Call 1 as a dynamic Bearer header value. Store the token in a Bubble database field or page custom state between workflow steps. Note: the Docker Hub login credentials (password) should be stored as a Private field in the API Connector or as a Bubble database field with appropriate Privacy Rules.

```
POST https://hub.docker.com/v2/users/login
Content-Type: application/json

{
  "username": "<username - Private>",
  "password": "<password - Private>"
}

# Response: {"token": "jwt-token-here"}
# Use in next call: Authorization: Bearer <result from step 1's token>
```

### Docker container is running but Bubble cannot reach it — Initialize call fails with 'connection refused'

Cause: The container is binding to 127.0.0.1 (localhost) instead of 0.0.0.0, making it unreachable from outside the container. Or, the cloud platform has not assigned a public URL yet, or a firewall is blocking the port.

Solution: In your Dockerfile CMD or in your application code, ensure the server binds to 0.0.0.0, not 127.0.0.1. Example: 'uvicorn main:app --host 0.0.0.0 --port 8000' or 'app.listen(8000, '0.0.0.0')'. Redeploy the container after this change. Also confirm your cloud platform has assigned a public domain and the service is in 'running' state.

```
# FastAPI / Uvicorn — bind to all interfaces
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

# Node.js / Express — bind to all interfaces
app.listen(8000, '0.0.0.0', () => console.log('Server running'));
```

## Frequently asked questions

### Can I run Bubble inside a Docker container?

No. Bubble is a hosted SaaS platform — your Bubble app lives on Bubble's cloud infrastructure and is accessed through a browser. There are no source code files to containerize. The correct integration pattern is the reverse: your Docker container runs a microservice that Bubble calls via API Connector.

### My Docker container is running on localhost — why can't Bubble reach it?

Bubble's API Connector makes calls from Bubble's cloud servers, not from your local machine or the end user's browser. A container running on localhost is accessible only from the same machine. You must deploy the container to a cloud platform (Railway, Render, Fly.io, AWS ECS, Google Cloud Run) that gives it a public HTTPS URL before Bubble can reach it.

### Does Bubble's API Connector work with self-signed TLS certificates on Docker containers?

No. Bubble's API Connector validates TLS certificates against public Certificate Authorities. Self-signed certificates (generated with openssl for local development) are rejected because they are not trusted by the public CA chain that Bubble's servers verify against. Use a cloud platform that automatically provisions Let's Encrypt or equivalent certificates for your Docker service's domain.

### What happens if my Docker container is slow and Bubble's 30-second timeout hits?

The Workflow step will fail with a timeout error, and Bubble will skip any subsequent steps in that workflow branch. To handle long-running processes, implement an async job pattern: your container immediately returns a job ID, and a separate Bubble workflow polls a status endpoint at intervals (using Schedule API Workflow on paid Bubble plans) until the job is marked complete.

### Which cloud platform is best for deploying Docker containers that Bubble calls?

Railway and Render are the easiest options for most founders — both detect your Dockerfile automatically, deploy your container, and provide HTTPS URLs within minutes. Railway has a $5/month minimum after a free trial; Render has a free tier but free services spin down after 15 minutes of inactivity (causing cold start delays that can hit Bubble's 30-second timeout). For production apps with consistent traffic, Fly.io, AWS ECS with Fargate, or Google Cloud Run are excellent choices.

---

Source: https://www.rapidevelopers.com/bubble-integrations/docker
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/docker
