A simple Python API to run and control Docker containers and optionally proxy requests to a container's published port.
Built with FastAPI and the Docker SDK for Python.
- Pull Docker images
- Run containers with a selected container port published to a host port (auto-assign or specify)
- List, inspect, start, stop, and remove containers (labeled as managed by this API)
- Reverse-proxy endpoint to interact with the container's API via this service
- CORS enabled for easy local testing
- Windows with Docker Desktop (or any OS with a working Docker engine)
- Python 3.10+
PowerShell commands (pwsh):
# From the repo root
cd dockAPI
# Create and activate a virtual environment (recommended)
python -m venv .venv
. .venv\Scripts\Activate.ps1
# Install dependencies
pip install -r requirements.txt
# Start the API (http://127.0.0.1:8000)
uvicorn app.main:app --reload --host 127.0.0.1 --port 8000Open the interactive docs: http://127.0.0.1:8000/docs
- GET
/healthz— Docker connectivity check - GET
/images— List images - POST
/images/pull— Pull an image - GET
/containers— List managed containers - POST
/containers/run— Run a container and publish a port - GET
/containers/{id}— Inspect container - POST
/containers/{id}/stop— Stop - POST
/containers/{id}/start— Start - DELETE
/containers/{id}— Remove - GET
/containers/{id}/logs— View logs (tail, follow) - POST
/containers/{id}/exec— Run a command inside the container - GET
/proxy/{id}— Show the upstream URL for the container - ANY
/proxy/{id}/{path}— Reverse-proxy to the container's published host port
All containers created by this API are labeled with dockapi.managed=true.
Quick links:
- Swagger UI: http://127.0.0.1:8000/docs
- ReDoc: http://127.0.0.1:8000/redoc
- OpenAPI JSON: http://127.0.0.1:8000/openapi.json
Below runs the official nginx:latest image, publishing container port 80 to an auto-assigned local host port, then accesses it via the proxy.
# 1) Run a container
$body = @{
image = "nginx:latest"
container_port = 80
# host_port = 8080 # optional: force a host port
name = "my-nginx" # optional
} | ConvertTo-Json
Invoke-RestMethod -Method POST `
-Uri http://127.0.0.1:8000/containers/run `
-ContentType 'application/json' `
-Body $body
# Response example
# {
# "id": "<container-id>",
# "name": "my-nginx",
# "image": "nginx:latest",
# "status": "running",
# "labels": {"dockapi.managed":"true","dockapi.name":"my-nginx","dockapi.container_port":"80"},
# "host_port": 52347,
# "container_port": 80
# }
# 2) Discover the upstream URL
Invoke-RestMethod -Uri http://127.0.0.1:8000/proxy/<container-id>
# => { "container_id":"<container-id>", "upstream":"http://127.0.0.1:<host_port>" }
# 3) Access the container through the dockAPI proxy
Invoke-WebRequest `
-Uri http://127.0.0.1:8000/proxy/<container-id>/ `
-Method GETYou can also call the container directly at http://127.0.0.1:<host_port>/... using the host_port in the run response.
POST /containers/run
{
"image": "org/image:tag",
"container_port": 8080,
"host_port": 0,
"name": "optional-name",
"env": {"KEY":"VALUE"},
"command": ["optional", "override"],
"auto_remove": true,
"detach": true,
"restart_policy": "unless-stopped"
,
"volumes": ["C:/host/data:/data:ro"],
"network": "my-network",
"wait_ready": true,
"health_path": "/healthz",
"wait_timeout": 30
}Notes:
- If
host_portis omitted or set to 0, the API auto-assigns a free localhost port. - Only the specified
container_port(TCP) is published. - The reverse proxy routes requests to
http://127.0.0.1:<host_port>using the same method, headers (minus hop-by-hop), query, and body. - If
wait_readyis true andhealth_pathis provided, the API pollshttp://127.0.0.1:<host_port><health_path>until it returns 2xx or timeout.
curl -X POST \
http://127.0.0.1:8000/containers/run \
-H 'content-type: application/json' \
-d '{
"image": "nginx:latest",
"container_port": 80,
"host_port": 0,
"name": "nginx-demo",
"wait_ready": true,
"health_path": "/"
}'$body = @{
image = "nginx:latest"
container_port = 80
volumes = @("C:/host/data:/usr/share/nginx/html:ro")
} | ConvertTo-Json
Invoke-RestMethod -Method POST `
-Uri http://127.0.0.1:8000/containers/run `
-ContentType 'application/json' `
-Body $body- Allowed methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
- Headers: Hop-by-hop headers (Connection, TE, etc.) are stripped; Host header is set by the proxy.
- Path joining:
/proxy/{id}/{path}maps tohttp://127.0.0.1:<host_port>/{path}
Examples:
# GET with query string
curl "http://127.0.0.1:8000/proxy/<container-id>/api/items?page=1&limit=20"
# POST JSON body to container via proxy
curl -X POST \
"http://127.0.0.1:8000/proxy/<container-id>/api/items" \
-H 'content-type: application/json' \
-d '{"name":"demo"}'- GET
/containers/{id}/logs?tail=200&follow=false tail: number of lines from the end (optional)follow: if true, streams logs (text/plain)
# Last 100 lines
curl "http://127.0.0.1:8000/containers/<container-id>/logs?tail=100"
# Stream logs (press Ctrl+C to stop)
curl -N "http://127.0.0.1:8000/containers/<container-id>/logs?follow=true&tail=100"- POST
/containers/{id}/exec
Request:
{
"command": ["ls", "-la", "/"],
"workdir": "/",
"env": {"DEMO": "1"},
"tty": false
}Response:
{
"id": "<container-id>",
"exit_code": 0,
"stdout": "...",
"stderr": null
}PowerShell tip: Arrays are easy with ConvertTo-Json. If you prefer a single string command, the API accepts that too, e.g. "command": "ls -la /".
- 400 Bad Request — invalid parameters, image not found, port conflicts, invalid volume format
- 404 Not Found — container not found
- 500 Internal Server Error — unexpected Docker/engine error
- 502 Bad Gateway — upstream (proxied container) request failed
- 504 Gateway Timeout — readiness check timed out when
wait_ready=true
Common causes:
- Docker is not running or not reachable (start Docker Desktop)
- Image requires authentication (log into registry in Docker Desktop or CLI)
- Host port already in use (choose another port or set
host_port: 0) - Volume path doesn’t exist or is not shared with Docker on Windows
- Windows volume bind: ensure your drive is shared in Docker Desktop Settings → Resources → File Sharing.
- WSL2 networking: mapped ports are reachable from Windows at 127.0.0.1 by default.
- Private registries:
docker loginwith the same daemon this API uses. - Slow pull: large images over slow networks can delay
/images/pulland/containers/run. - Health path wrong: if
wait_ready=trueand you get 504, open the upstream URL from/proxy/{id}and verify thehealth_path.
- Ensure Docker Desktop is running and the engine is set to expose localhost ports
- If you run Docker in WSL2, published ports are also reachable from Windows at 127.0.0.1
cd dockAPI
. .venv\Scripts\Activate.ps1
uvicorn app.main:app --reloadThen open http://127.0.0.1:8000/docs.
MIT