See it as a diagram
Everything below, as a diagram you can edit. Describe yours and see it in seconds.
No account needed · Editable canvas, not a picture
Three ways to put a picture in a notebook, and where each one stores it
The first is a markdown cell with an image address. Nothing is stored in the notebook except the URL, and the renderer fetches it every time the cell displays. The picture can change without the notebook changing.
The second is dragging a file into a markdown cell. Jupyter encodes it as base64 and writes it into the notebook JSON as an attachment, referenced through the attachment scheme. The image now travels with the file and never updates again.
The third is code: IPython.display.Image with a url argument emits an img tag pointing at the address, while the same class with a filename argument reads the bytes and stores them in the cell output, which then gets saved into the notebook. Two very similar lines with opposite consequences.
from IPython.display import Image, IFrame
# Points at the address, re-fetched on every render
Image(url="https://datadef.io/api/embed/my-platform-a1b2c3d4")
# Pan and zoom inside JupyterLab
IFrame("https://datadef.io/embed/my-platform-a1b2c3d4", width="100%", height=600)Why base64 attachments hurt in review
A notebook is JSON under version control, and an inlined PNG lands as one enormous line of base64. Every re-save rewrites it, the diff view gives up, and reviewers scroll past a wall of characters looking for the two lines of code that actually changed. Teams that have adopted nbdime or a stripping pre-commit hook know this pain already.
An address costs about eighty characters, produces a readable diff, and keeps the repository small. For a diagram that is regenerated regularly, it also means the notebook does not need to be touched when the architecture moves.
The trade is real and worth naming: an attachment renders with no network, and an address does not. For a notebook shipped to an air-gapped environment or archived for compliance, the frozen copy is the correct choice.
What GitHub does with a committed notebook
GitHub renders .ipynb files, and external images inside them are routed through the same anonymizing image proxy as a README, so caching behaves the way it does there: the proxy fetches from the origin and honours the origin cache headers. The embed endpoint sends max-age 300, so an edited diagram shows up in the rendered notebook within minutes.
HTML output is sanitized, which is where the two paths diverge. An IFrame call gives you a pannable diagram in JupyterLab and shows nothing in the GitHub file view, because the frame is stripped. If the notebook is meant to be read on GitHub, put the image in a markdown cell and keep the frame for interactive work.
Attachment images are stored in the notebook and render on GitHub, at the cost of the diff problem above. There is no option that is simultaneously light, interactive and offline.
Freezing on purpose for a handout
When a notebook becomes a deliverable, the network dependency stops being a feature. nbconvert can inline every image as a data URI while converting to HTML, with the embed-images flag, so the exported file opens anywhere with no requests going out.
That gives a clean split: the working notebook in the repository carries addresses and stays current, the exported artifact carries bytes and stays fixed. Nobody has to choose one policy for both.
jupyter nbconvert --to html --embed-images analysis.ipynb
The diagram a data notebook needs
Notebook readers are usually asking where the data came from. That makes the useful picture a pipeline or a lineage view rather than a service map: sources, ingestion, the warehouse layers, and the specific tables this notebook reads. Datadef draws column-level lineage as first-class relationships, which is closer to the question than boxes and arrows. See lineage.
Keep the diagram in one place and reference it from every notebook that needs it. Ten notebooks each carrying their own base64 copy of last year architecture is the exact failure this page exists to prevent.
Legibility and completeness are handled separately, which matters for an audience that will notice a missing table. Repeated minor resources of the same type inside a module collapse into one node carrying its count and its members, labelled like Storage credentials ×2 with external and root named underneath, and the tail that still does not fit becomes a single supporting-resources node. Nothing is discarded in the process: the counts ride on the canvas and the full inventory lands in the module reference table of the generated architecture.md, which is the document to open when the picture has deliberately compressed something you were looking for.
Keeping it current
FAQ
How do I show an image from a URL in a Jupyter notebook?
Why do images disappear when my notebook is viewed on GitHub?
Should I drag an image into a notebook or link it?
Can I get a pannable diagram inside JupyterLab?
How do I export a notebook with the images frozen?