Skip to content

Commit 213ccea

Browse files
jamesbhobbsclaudetkislan
authored
docs: add public pages for the deepnote publish and sync CLI commands (#510)
* fix(cli): let sync and publish share one baseline for project files `deepnote publish` deploys into `_deepnote_static/**`, which is a subtree of the project file store `deepnote sync --all-files` mirrors. Neither command knew about the other, so they drifted: every publish made the whole static subtree look changed to sync (re-downloading it on the next run), a stale local mirror could be pushed back over a live site with no staleness check, and `publish --prune` left local ghosts that a later edit would resurrect. Resolved by coordination rather than by dividing the namespace, so both commands keep working on the same paths: - Sync gains per-file lost-update protection on push. `uploadProjectFiles` never fetched the inventory at all; it now checks every candidate against it and routes a file that moved since the manifest baseline through the existing `--on-conflict` override-or-skip choice. A pending replacement is exempt — that missing cloud copy is sync's own unfinished delete. This also fixes the same silent overwrite for ordinary working files edited in the Deepnote app. - Publish updates the sync mirror when the published directory sits inside a synced workspace: it writes each file into the project's `.files/` mirror and records size, hash, and server `updatedAt`, exactly as a sync download would. `--prune` drops pruned paths from both. `--sync-root`/`--no-sync-root` control discovery. - Publish stops before mutating anything if a path it would write has moved on in Deepnote since that workspace last synced, since the mirror holds no copy of that content; `--force` overrides. Its check is deliberately narrower than sync's: publish is a deploy where the local build is authoritative, so a path with no baseline is not flagged. - `PROJECT_STATIC_ROOT` moves to `@deepnote/cloud` so every writer agrees on where the boundary is. The mirror is only updated when the tracked project directory already exists — creating it would make the next sync read the project as "all notebooks deleted locally" and push that. Mirror failures are warnings, not errors: the deploy succeeded, and a stale manifest is safe because the next sync asks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: add public pages for the deepnote publish and sync CLI commands Neither command had public documentation, and "deepnote sync" was easy to confuse with the in-product "Deepnote file sync" feature since the names collide and nothing distinguished them. - docs/deepnote-cli-publish.md — deploying a build directory to a project: authentication and token safety, finding a project ID, the _deepnote_static target and --path, --api-access and why it is off by default, --prune, failure ordering, exit codes, and the canonical URL the server returns. - docs/deepnote-cli-sync.md — mirroring a workspace locally: the directory layout and manifest, both sync directions, conflict handling, --all-files, the deletion rules, safety rails, and automation. - docs/deepnote-file-sync.md — a callout up top distinguishing the in-product Git-linked feature from the CLI command, plus cross-links. Both new pages document ownership of the static site directory: publish is the write path, and sync mirrors it without ever silently overwriting it, surfacing a republished site as a conflict instead. A publish inside a synced workspace keeps that workspace's mirror in step. Deliberately untouched: docs/deepnote-mcp.md (waiting on the released manifest), and docs/creating-apps.md, docs/streamlit.md, docs/scheduling.md, docs/export-pdf.md and docs/export-project.md (PR #491 and the app-reference work). No tool counts or unreleased capabilities are described. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: correct the access model for published static sites The publish page described a published site as a "public static website" and its contents as "publicly readable". That was wrong. Static file sharing has no anonymous tier and no link-only tier: an unauthenticated visitor is redirected to sign-in, and the viewer must additionally be an active user with view access to the project. A workspace-level setting and plan availability can each disable it independently, and access is re-checked on every request. Adds a "Who can view a published site" section stating those requirements, and points readers at data apps — which do offer "Anyone with a link" and "Public" access levels — when the deliverable has to reach people without Deepnote accounts. That contrast is the thing most likely to be assumed wrongly, since "publish" implies public hosting elsewhere. Also notes that API access can only narrow the audience rather than widen it, and that the printed URL must be used verbatim because each project's site is served from its own origin. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(publish): narrow the DEEPNOTE_TOKEN exposure claim Say the environment variable keeps the token out of the command line rather than claiming it avoids shell-history and process-list exposure entirely. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: tomas <tomas@kislan.sk>
1 parent 8cd8832 commit 213ccea

3 files changed

Lines changed: 507 additions & 0 deletions

File tree

‎docs/deepnote-cli-publish.md‎

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
---
2+
title: Publishing static sites with the Deepnote CLI
3+
description: Deploy a local build directory to a Deepnote project as a hosted static website using deepnote publish
4+
noIndex: false
5+
noContent: false
6+
---
7+
8+
`deepnote publish` uploads a local directory — a Vite or Next.js build, a hand-written page, a
9+
documentation site — to an existing Deepnote project and serves it as a static website. The command
10+
uploads the files, enables static website sharing, and prints the canonical URL Deepnote assigns.
11+
12+
```bash
13+
deepnote publish ./dist --project-id <project-id>
14+
```
15+
16+
This is a command-line deploy path, separate from publishing an app from inside the Deepnote editor.
17+
It writes plain files; it does not create or run notebooks.
18+
19+
<Callout status="warning">
20+
A published static site is **not anonymously public**. Viewers must be signed in to Deepnote and have
21+
access to the project. See [Who can view a published site](#who-can-view-a-published-site) — the
22+
access model differs from [data apps](/docs/data-apps), which do offer public and link-only sharing.
23+
</Callout>
24+
25+
## Prerequisites
26+
27+
- **An existing Deepnote project.** `deepnote publish` never creates one. Create the project in
28+
Deepnote first and copy its ID.
29+
- **The Deepnote CLI.** Install it with `npm install -g @deepnote/cli` (or run it through
30+
`npx @deepnote/cli`).
31+
- **An API token** with access to that project.
32+
33+
## Authentication
34+
35+
The CLI reads your token from the `DEEPNOTE_TOKEN` environment variable, or from an explicit
36+
`--token` flag. Create a token in your workspace under
37+
[Settings & members → API tokens](https://deepnote.com/workspace/settings/api-tokens).
38+
39+
```bash
40+
export DEEPNOTE_TOKEN="<your-token>"
41+
deepnote publish ./dist --project-id <project-id>
42+
```
43+
44+
Without a token the command exits with code `2` and prints where to get one.
45+
46+
### Token safety
47+
48+
<Callout status="warning">
49+
An API token carries your access to the workspace. Treat it like a password.
50+
</Callout>
51+
52+
- **Prefer the environment variable.** A token passed as `--token` is visible in your shell history
53+
and in the process list of a shared machine. `DEEPNOTE_TOKEN` keeps it out of the command line.
54+
- **In CI, use a secret.** Store the token in your CI provider's secret store and expose it as
55+
`DEEPNOTE_TOKEN` for the publish step only. Never commit it to the repository you are deploying.
56+
- **Rotate and revoke** from the same settings page if a token is ever exposed.
57+
- **Keep it out of the build directory.** Everything under the directory you publish becomes readable
58+
at the site URL by anyone who can view the site — including dotfiles, source maps, and stray `.env`
59+
files. Publish a clean build output directory, not a project root.
60+
61+
## Finding a project ID
62+
63+
Open the project in Deepnote and read the `project_id` — a UUID — from the URL. The general structure
64+
when editing a notebook is:
65+
66+
```
67+
https://deepnote.com/workspace/<workspace_name>-<workspace_id>/project/<project_name>-<project_id>/notebook/<notebook_name>-<notebook_id>
68+
```
69+
70+
That `project_id` is the value for `--project-id`. Inside a running notebook it is also available as
71+
the `DEEPNOTE_PROJECT_ID` environment variable, which is handy if you script the deploy from the
72+
project itself.
73+
74+
## Where the files go
75+
76+
Published files live under a reserved directory in the project's file store called
77+
`_deepnote_static`. Publishing `./dist/index.html` puts the file at `_deepnote_static/index.html`,
78+
and that path is what the site serves.
79+
80+
Use `--path` to publish below a subdirectory of the static root — useful for keeping versions side by
81+
side:
82+
83+
```bash
84+
deepnote publish ./dist --project-id <project-id> --path _deepnote_static/v2
85+
```
86+
87+
`--path` must be `_deepnote_static` or a directory under it; anything else is rejected before the
88+
command touches the project. Nested path segments are percent-encoded in the printed URL, so a path
89+
containing `#` or `?` still yields a working link.
90+
91+
Always use the URL the command prints rather than assembling one yourself. Deepnote serves each
92+
project's site from its own dedicated origin and hands out the shareable link on the main domain, so
93+
a hand-built URL is unlikely to resolve.
94+
95+
## Who can view a published site
96+
97+
Static site sharing is **not** public hosting. Every viewer must be:
98+
99+
1. **Signed in to Deepnote** — anonymous visitors are redirected to sign-in, never served content.
100+
2. **An active user** — suspended or deactivated accounts are refused.
101+
3. **Able to view the project** — workspace members, project collaborators (including app users),
102+
and user groups with project access.
103+
104+
Two further switches can turn a site off independently of the project setting: a workspace-level
105+
static file sharing setting, and plan availability for the feature. Access is re-checked on every
106+
request, so revoking any of these — the project toggle, the workspace setting, the viewer's account,
107+
or the plan — takes effect immediately rather than at the next deploy.
108+
109+
<Callout status="warning">
110+
There is no anonymous tier and no link-only tier for static sites. A link alone never grants access,
111+
so you cannot use `deepnote publish` to serve a page to the general public, to an unauthenticated
112+
webhook consumer, or to a search engine crawler.
113+
</Callout>
114+
115+
This is the main difference from [data apps](/docs/data-apps), which do offer **Anyone with a link**
116+
and **Public** access levels. If your deliverable has to reach people without Deepnote accounts, a
117+
data app is the model that supports it — not a published static site.
118+
119+
## Options
120+
121+
| Option | Description | Default |
122+
| -------------------------------- | ------------------------------------------------------------------------ | --------------------------- |
123+
| `--project-id <id>` | Project to publish to (required) | |
124+
| `--path <prefix>` | Target directory at or below `_deepnote_static` | `_deepnote_static` |
125+
| `--api-access enabled\|disabled` | Explicitly enable or disable Deepnote API access for the site | unchanged |
126+
| `--prune` | Delete remote files below `--path` that are absent from the local build | `false` |
127+
| `--sync-root <dir>` | Sync workspace whose local mirror to update | search upwards from `<dir>` |
128+
| `--no-sync-root` | Publish without looking for or updating a sync workspace | `false` |
129+
| `--force` | Publish even when Deepnote holds changes a sync workspace has not pulled | `false` |
130+
| `--token <token>` | API token | `DEEPNOTE_TOKEN` |
131+
| `--url <url>` | API base URL (for single-tenant instances) | `https://api.deepnote.com` |
132+
| `-q, --quiet` | Suppress progress output; errors still print to stderr | `false` |
133+
134+
## API access for published sites
135+
136+
By default a published site is a plain static website: it can serve HTML, CSS, JavaScript, and
137+
assets, but it cannot call the Deepnote API.
138+
139+
Passing `--api-access enabled` lets the page acquire a short-lived, project- and viewer-scoped token
140+
from the Deepnote shell that embeds it. That token has a deliberately narrow surface — read the
141+
configured notebook, start a run, poll that run — which is what makes an interactive page possible
142+
without a server of your own.
143+
144+
This is a second opt-in layered on top of site sharing, and it can only ever narrow the audience, not
145+
widen it: a viewer who cannot see the site cannot obtain a token for it. Because every viewer is a
146+
signed-in user with project access, the token is minted for that identity.
147+
148+
<Callout status="warning">
149+
API access is security-sensitive and is never enabled implicitly. Enable it only when the page needs
150+
to load notebooks or start runs, and remember that anyone who can view the site can exercise that
151+
access.
152+
</Callout>
153+
154+
Omitting the flag leaves the project's current setting untouched, so a routine redeploy cannot
155+
silently turn API access on or off. Pass `--api-access disabled` to turn it off explicitly.
156+
157+
## Removing files from an earlier build
158+
159+
By default publish replaces the files present in your local directory and leaves everything else
160+
alone, so assets from an older build accumulate. `--prune` deletes remote files below `--path` that
161+
are absent locally:
162+
163+
```bash
164+
deepnote publish ./dist --project-id <project-id> --prune
165+
```
166+
167+
Pruning is ordered so a failed deploy cannot leave the site half-deleted. Stale paths that block a
168+
directory the new build needs are removed first, because the upload cannot proceed without them.
169+
Every other stale file is removed only after all uploads have succeeded.
170+
171+
<Callout status="info">
172+
`deepnote publish --prune` deletes **remote** files that are missing locally. The unrelated
173+
[`deepnote sync --prune`](/docs/deepnote-cli-sync) deletes **local** files that are missing in the
174+
cloud. The two flags share a name and point in opposite directions.
175+
</Callout>
176+
177+
## Failure behavior
178+
179+
The command validates everything it can locally before touching the project, then makes remote
180+
changes in a fixed order.
181+
182+
- **Local path problems abort before any upload.** A filename with a leading or trailing space, a
183+
backslash, or two local files that would collide at the same remote path all stop the command with
184+
exit code `2` and an unchanged project.
185+
- **Each file is read before its remote copy is replaced**, so an unreadable local file leaves the
186+
live version intact.
187+
- **Website sharing is enabled only after every upload succeeds.** A partial upload is reported as a
188+
failure and does not flip the sharing setting or prune remaining stale files.
189+
- **A failed file is reported individually** and the command continues with the rest, then exits
190+
with code `1`. Successful uploads are not rolled back — re-run the command once the cause is fixed.
191+
192+
### Exit codes
193+
194+
| Code | Meaning |
195+
| ---- | -------------------------------------------------------------------------------------------------------- |
196+
| `0` | Files uploaded and website sharing enabled |
197+
| `1` | A project lookup, upload, prune, or settings update failed, or Deepnote holds changes not pulled locally |
198+
| `2` | Invalid usage — bad `--path`, missing directory, missing token, or an unusable `--sync-root` |
199+
200+
<Callout status="info">
201+
Each file is replaced with a delete followed by an upload, so a file being overwritten is briefly
202+
unavailable on the live site. Publish during a quiet window if that matters for your deployment.
203+
</Callout>
204+
205+
## Working alongside `deepnote sync`
206+
207+
`_deepnote_static` is part of the same project file store that
208+
[`deepnote sync --all-files`](/docs/deepnote-cli-sync) mirrors, so both commands can write those
209+
paths. They coordinate rather than divide the namespace:
210+
211+
- **`deepnote publish` is the write path for the static root.** It is the command that deploys a
212+
build, and in practice the only one that should be authoring those files.
213+
- **`deepnote sync` mirrors them but never silently overwrites them.** Before pushing any working
214+
file it checks the current state in Deepnote, and a file that changed since it last synced is
215+
surfaced as a conflict to resolve rather than overwritten.
216+
- **When you publish from inside a synced workspace**, publish also updates that workspace's local
217+
mirror and its `.deepnote-sync.json`, so the next sync sees the deploy as already up to date
218+
instead of re-downloading the whole site.
219+
- **If Deepnote holds changes your workspace has not pulled**, publish stops before writing anything
220+
rather than destroying content you have no local copy of. Run `deepnote sync --all-files` to bring
221+
it down, or pass `--force` to overwrite.
222+
223+
Use `--no-sync-root` for a CI deploy, where there is no workspace to keep in step and the extra
224+
lookup is pointless.
225+
226+
## Related
227+
228+
- [Syncing a workspace with the Deepnote CLI](/docs/deepnote-cli-sync) — mirror projects to a local
229+
directory and push notebook edits back
230+
- [Deepnote file sync](/docs/deepnote-file-sync) — the in-product feature that keeps a project synced
231+
with a `.deepnote` file in a Git repository
232+
- [Data apps](/docs/data-apps) — building interactive apps on Deepnote

0 commit comments

Comments
 (0)