Jupyter Notebook on a Mac: the notebook out of the browser
Jupyter on a Mac works the first time, which is why the install guides are short and the frustration comes later. A notebook opens at localhost in tab twenty-three, next to a documentation page, a pull request and three Stack Overflow answers. Two hours in, a stray Cmd+W closes the notebook instead of the search result, the kernel is still alive but the window pointing at it is gone, and the analysis has to be found again by URL. The install was never the problem. The container was.
The three ways Jupyter gets onto a Mac
Project Jupyter documents two of them and maintains the third separately. They produce genuinely different things.
Project Jupyter's tools are available for installation via the Python Package Index, the leading repository of software created for the Python programming language. Source: jupyter.org
The install page names pip as the recommended tool and gives two commands worth telling apart. pip install jupyterlab followed by jupyter lab gives the current interface. pip install notebook followed by jupyter notebook gives the classic Notebook interface, which still exists and is still maintained. The page also points at conda, mamba, pipenv and Homebrew for anyone who needs environment management rather than a bare install.
The Homebrew route is one line on the same page.
Homebrew is a package manager for macOS and Linux. You can use it to install Jupyter by running: brew install jupyterlab Source: jupyter.org
The third route is a real application. JupyterLab Desktop is maintained by the JupyterLab project itself and described in its own words as the cross-platform desktop application for JupyterLab, offered as a Mac installer for macOS 12 or later, with separate downloads for Apple silicon and Intel chips. It carries a bundled Python environment, so it does not require a working Python setup before it will open a notebook.
| pip or Homebrew | JupyterLab Desktop | |
|---|---|---|
| Where the notebook appears | Browser tab | Application window |
| Python environment | Whichever one installed it | Bundled, plus any added |
| Dock icon and Cmd+Tab | No | Yes |
| Opens .ipynb by double click | No | Yes |
| Extension support | Full | Prebuilt extensions only |
| Minimum macOS | Whatever Python supports | macOS 12 |
Three interfaces, one install page
The same page lists a third tool that is easy to skip and worth knowing about. pip install voila, launched with voila, serves a notebook as a page with the code cells hidden, leaving only the widgets and the output. For anyone who has been asked to hand a notebook to a colleague who does not want to read Python, that is the answer, and it produces exactly the kind of single-purpose page that belongs in a window rather than a tab.
Choosing between JupyterLab and the classic Notebook interface is less consequential than it looks. Both install from the same index, both run the same kernels and open the same .ipynb files, and the classic interface remains a documented option rather than a deprecated one. JupyterLab is the default answer because it holds several notebooks, a terminal and a file browser in one layout, which is the layout a dedicated window suits best.
What "running Jupyter" actually means
The confusion that drives most of this search is that Jupyter is not a browser application. It is a local web server, and the browser is only its client. The Jupyter Server documentation is direct about it.
By default, Jupyter Server runs locally at 127.0.0.1:8888 and is accessible only from localhost. You may access the server from the browser using http://127.0.0.1:8888. Source: jupyter-server.readthedocs.io
Three consequences follow, and they explain most of the odd behaviour people report.
Closing the tab does not stop anything. The server keeps running, the kernel keeps its variables, the long training loop keeps looping. Reopening localhost:8888 reconnects to the same session. The panic that follows a closed tab is misplaced, though finding the URL again is still an interruption.
Quitting the terminal, on the other hand, usually does stop it, because the server is a foreground process in that shell. That is the asymmetry that catches people: the browser is disposable and the terminal is not, which is the reverse of how it feels.
The second consequence is about ports. The documentation notes that kernels communicate with the server over randomly chosen ports in the 49152 to 65535 range, from localhost. That is why a firewall or network tool that is fine with port 8888 can still leave a notebook stuck on "connecting to kernel".
The third is about authentication. Access is protected by a token by default, which is why the first launch prints a long URL with a query string attached. The same documentation notes that since notebook 5.3, the first token login offers the chance to set a password from the interface instead. That detail matters later.
What JupyterLab Desktop gains and gives up
The desktop application answers the original request directly. It has a Dock icon, it answers to Cmd+Tab, double clicking an .ipynb file opens it, and a jlab command launches it from a terminal with a directory or file as an argument. Each working directory is treated as a separate project with its own Python environment and layout, and previous sessions are listed on the welcome page so a week-old analysis reopens in one click.
It also has a feature that is easy to miss and solves a real problem. A Connect... option starts a session against an existing JupyterLab server, local or remote, and locally running servers are detected and listed automatically. So the application can be a front end for a server started by hand in a terminal, or for one running on a workstation across the room, rather than only for its own bundled environment.
The trade-off is stated in the project's own documentation: only prebuilt extensions are supported, and source extensions that require a rebuild are not. Anyone whose workflow depends on a rebuilt extension will hit that wall, and the answer is the pip route rather than a workaround.
The other trade-off is subtler. A bundled environment is a second Python installation on a machine that already has one or three, and environment confusion is the single most common source of "the import works in the terminal but not in the notebook". The application does offer environment management, including adding an existing environment by pointing at its python executable, which is the setting to reach for before debugging anything else.
Why a notebook and a browser tab are a poor match
Set aside the install question and the daily friction is about identity and durability.
Two notebooks look identical in a tab strip
Browser tabs are narrow. A tab titled with a truncated notebook name, next to another tab titled with a different truncated notebook name, is a guess. Running two analyses at once, which is normal, means the tab strip stops being navigable exactly when navigation matters.
Cmd+W is unguarded
The frontmost tab closes without confirmation. The kernel survives, but the scroll position, the collapsed cells and the output nobody re-ran do not, and reconnecting means finding the URL again. A window containing one thing cannot lose that thing by accident, because there is nothing else in it to close instead.
The tab strip is shared with everything else
A notebook competes for space with the documentation being read while writing it. Thirty tabs in, the notebook is off the edge of the strip, and the search for it takes longer than the cell being written. This is the specific problem a separate window solves, and it is worth more than it sounds for work that runs for days.
Giving localhost its own window
macOS has a route for this that costs nothing, and Apple is precise about what it produces.
A web app functions independently of Safari. It shares no browsing history, cookies, website data, or settings with Safari. Source: support.apple.com
In Safari the path is File then Add to Dock, and Apple's article, which requires macOS Sonoma 14 or later, states that the result is saved to the Applications folder of the home folder. It appears in Spotlight, keeps a Dock position, answers to Cmd+Tab, and its settings panel allows the name, the icon and the URL to be changed, with an option to hide the navigation controls entirely.
There is one Jupyter-specific catch, and it is the reason the token detail above matters. A window with a fixed URL cannot follow a token that changes on every server restart. The fix is the one the Jupyter documentation already describes: set a password on the first token login, then point the window at http://localhost:8888/ and sign in with the password each time. That converts a disposable URL into a stable one, which is the precondition for a window that is worth keeping.
A site to app tool covers what the built-in route does not: a window with a session store of its own rather than one shared with the browser, more than one window against the same host for different ports or projects, and control over what the window is permitted to do. The supported services list shows the same pattern applied to hosted tools, and a local notebook server behaves the same way from the window's point of view.
What to check in the first ten minutes
Test three things. Whether the window reconnects cleanly after the server is restarted, which is the case a fixed URL has to survive. Whether file downloads from a notebook land where expected rather than being blocked. And whether the keyboard shortcuts that matter, particularly Shift and Return to run a cell, reach the window without the browser intercepting them.
Two servers usually means two windows
Anyone who runs more than one project runs more than one server. A second jupyter lab on port 8889, a JupyterHub instance at work, a notebook on a remote machine reached through a tunnel. All three are different URLs, and all three are the kind of thing that gets lost in a tab strip.
Separate windows, one per server, make the Dock the project switcher. It also removes a specific and expensive mistake: running a cell against the production data server while believing the window was pointed at the local one. Two windows with two icons and two names are harder to confuse than two tabs with truncated titles, and the Dock makes the current target visible at a glance.
What to change first
Pick one route and stop hedging: pip install jupyterlab if extensions matter, JupyterLab Desktop if a Dock icon matters more. Then set a password on the server so its URL stops changing, and if the notebook deserves a window rather than tab twenty-three, Kagemusha is one way to give it one.
Frequently asked questions
Is there an official Jupyter Notebook app for Mac?
Yes, in the form of JupyterLab Desktop, maintained by the JupyterLab project. It is offered as a Mac installer for macOS 12 or later, with separate downloads for Apple silicon and Intel chips, and it ships with a bundled Python environment. The trade-off documented by the project is that only prebuilt extensions are supported, so extensions requiring a rebuild need the pip install route instead.
Should Jupyter be installed with pip or Homebrew on a Mac?
Project Jupyter's install page names pip as the recommended tool and gives pip install jupyterlab as the command. The same page documents brew install jupyterlab for Homebrew users and points at conda, mamba and pipenv for anyone who needs environment management rather than a plain install. The choice mostly determines which Python environment the notebook can see.
Does closing the browser tab stop a Jupyter notebook?
No. The Jupyter Server documentation describes the server as running locally at 127.0.0.1:8888, with the browser acting as a client, so closing the tab leaves the server and its kernels running. Reopening the address reconnects to the same session. Quitting the terminal that started the server is what actually stops it, which is the opposite of what most people expect.
Why does the Jupyter URL have a long token attached to it?
Because access is protected by a token by default, and the token changes each time the server starts. That is fine for copying a link out of a terminal and awkward for anything that stores a fixed address. The Jupyter Server documentation notes that the first token login offers to set a password from the interface, after which http://localhost:8888/ becomes a stable address.
Can a dedicated window point at a remote Jupyter server?
Yes, and so can JupyterLab Desktop, whose Connect... option starts a session against an existing server running locally or remotely and lists locally running servers automatically. For a window, the requirement is the same as for any other site: a stable URL and a session it can hold. A server reached through an SSH tunnel appears as a localhost address and behaves like a local one.