A small Python/FastAPI app that calls a Foundry-deployed image model (DALL-E 3 or GPT-Image-1) via the standard OpenAI SDK and shows the results in an interactive web gallery.
This is POC sample #8 in the Embr fleet. Its purpose is to surface a specific platform gap, not to be a production-ready image service.
- A real Foundry image-generation deployment driven from an Embr-hosted
app, using
client.images.generate(...)against the OpenAI-compat/openai/v1endpoint (works for both DALL-E 3 and GPT-Image-1). - A grid UI that lets a user type a prompt, generate 1–4 images at three aspect ratios, and delete them.
- A clean separation between metadata (
GET /api/images) and binary payload (GET /api/images/{id}.png) so the JSON wire format stays small even when the gallery contains many high-res images.
The biggest finding: embr.yaml has no way to declare persistent object
storage. This sample keeps every generated image in process memory
(collections.deque(maxlen=50)). When the Embr container restarts — deploy,
scale-in, OOM, crash — every image is gone. The yellow banner at the top of
the UI calls this out, and /api/store/info returns
{ "type": "in-memory", "restartLossWarning": true }.
Imagined fix:
storage:
images:
type: blob
lifecycle: persistent…where Embr would provision a storage account, inject the connection
string / SAS URL via env var, and ideally expose a uniform SDK so apps don't
have to import azure-storage-blob directly. Today the customer would have
to BYO an Azure Storage account, add the connection string as a secret env
var, and write the integration themselves — at which point they're back to
solving infra problems instead of building the app.
Even with blob storage, an image gallery really wants a CDN-fronted public
URL per asset. Today the customer would have to provision Azure Front Door
or CloudFront themselves and wire it to their storage account. An Embr
assets: primitive that returned a CDN URL for a blob would close this gap.
Image generation is expensive — easily $0.04–$0.12 per image. There's no
Embr primitive today for declaring a per-app or per-user cost ceiling. A
hypothetical quota: { dailyImages: 100 } would let an app fail fast before
running up a bill.
A naive implementation would return b64_json directly in the
POST /api/images response. With n=4 at 1792×1024 that's easily ~10 MB
of JSON, which can blow past Embr's default ingress limits and is slow to
parse client-side. We mitigate by storing the bytes in memory and serving
them via a separate /api/images/{id}.png endpoint with Content-Type: image/png. This is a band-aid; native blob support (with CDN URLs) is the
real answer.
app/
├── foundry_image.py # OpenAI SDK against Foundry /openai/v1, returns ImageRecord
├── store.py # collections.deque(maxlen=50) — RAM-only by design
├── main.py # FastAPI routes
└── static/index.html # dark-theme gallery UI
| Method | Path | Description |
|---|---|---|
POST |
/api/images |
{ prompt, n?, size? } → list of ImageRecord metadata + gallery size |
GET |
/api/images |
Metadata only (no base64) for every image currently in memory |
GET |
/api/images/{id}.png |
Raw PNG bytes |
DELETE |
/api/images/{id} |
Remove one image from the gallery |
GET |
/api/store/info |
{ type: "in-memory", count, capacity, restartLossWarning: true } |
GET |
/api/diag |
Foundry config status + store info |
GET |
/health |
Liveness probe |
GET |
/ |
Gallery UI |
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
export FOUNDRY_BASE_URL='https://<your-resource>.openai.azure.com/openai/v1'
export FOUNDRY_API_KEY='<your-key>'
export FOUNDRY_IMAGE_MODEL_DEPLOYMENT='gpt-image-1' # or dall-e-3
uvicorn app.main:app --reload --port 8000Then open http://localhost:8000.
If you pass a base URL without /openai/v1, the client appends it for you,
so all of these work:
https://foo.openai.azure.comhttps://foo.openai.azure.com/openaihttps://foo.openai.azure.com/openai/v1
- Open your Foundry project in the portal.
- Go to Models + endpoints → Deploy model and pick either
gpt-image-1ordall-e-3(image-generation models — they appear in the "Image" filter). - Give the deployment a name, e.g.
gpt-image-1. Use this forFOUNDRY_IMAGE_MODEL_DEPLOYMENT. - Open the deployment, copy the Endpoint (it'll look like
https://<resource>.openai.azure.com/) and the API key. Use these forFOUNDRY_BASE_URLandFOUNDRY_API_KEY.
Note: DALL-E 3 only supports
n=1per call. The client silently clamps the count when the deployment name containsdall-e-3.
embr quickstart deploy embr-devs/embr-foundry-image-gallery-sample -i 120233234
embr variables set FOUNDRY_BASE_URL='https://<resource>.openai.azure.com/openai/v1'
embr variables set FOUNDRY_API_KEY='<key>'
embr variables set FOUNDRY_IMAGE_MODEL_DEPLOYMENT='gpt-image-1'- The gallery wipes on every container restart. This is not a bug — it's the entire point of this sample. See finding #1 above.
- DALL-E 3 ignores
n > 1; the app silently clamps. - Image generation latency is multiple seconds per image — be patient with the spinner.
- Not suitable for production. Use as a demo / platform-finding artifact only.