Remote-SSH into Snowflake from VS Code and Cursor
Your data science or machine learning project is already laid out a certain way. There's a repo cloned somewhere on your laptop, a terminal in one pane, an editor in another, a .venv you're happy with, extensions you've picked and tended to over the years, and an AI assistant sidebar you've configured. When you want to run a notebook against Snowflake data, you have two (bad) choices: pull a sample down to the laptop and hope it's representative, or open a browser tab and work in a UI that isn't your editor and doesn't have any of your scaffolding. The problem: leaving the workspace you've worked hard to build for your day to day.
Why Remote-SSH for heavy compute
SSH is short for Secure Shell, a cryptographic network protocol from the mid-1990s for opening a shell session on a remote machine over an encrypted channel. On the modern developer laptop, ssh user@host is still how you get a prompt on a server, and for most people that's where the mental model stops.
Microsoft's Remote-SSH extension pushed the pattern further. When you connect VS Code (or Cursor, or any of the VS Code forks) to a remote host over Remote-SSH, the editor UI runs on your laptop and a headless extension host runs on the remote machine. Your files, your terminal, your language servers, and your Python interpreter all live on the remote box. The editor is a thin window into a workspace that isn't local. From your point of view it feels like editing locally; from the code's point of view it's running end-to-end on the remote host.
This is a well-worn pattern by now. It's how GitHub Codespaces work. It's how JetBrains Gateway, Gitpod, Coder, and DevPod work. It's how ML engineers reach the GPU box in the lab, how backend engineers work against a beefier build server, and how anyone whose target runtime doesn't fit on a MacBook stays productive. Container-native teams reach for dev containers, which are the same pattern with the "remote host" being a container on your own machine or a container in the cloud.
The reason the pattern keeps getting reinvented is that it separates the two things developers want to keep separate: the editing experience (your extensions, your keybindings, your muscle memory) and the execution environment (the CPUs, the GPUs, the network access, the data locality). One stays on the laptop where you are; the other lives wherever the work actually needs to run.
The ceremony to get there, if you're doing it yourself, is real. Generate an SSH keypair. Get the public key onto the remote host. Add an entry to ~/.ssh/config with the hostname, user, port, and identity file. If there's a jump host in the way, add a ProxyJump directive. If the remote sits on a private subnet, sort out the tunneling. Install the Remote-SSH extension. Reopen the folder in the remote window. Wait for the extension host to install itself remotely. Reinstall your other extensions inside that remote environment, because they need to run on the remote host now. Once it works it's transparent, but a lot has to go right for it to work at all.
What Remote Development changes
Remote Development in the Snowflake extension for Visual Studio Code takes that whole ceremony and does it for you. You configure a compute pool, click to connect over SSH, and your editor opens into a Snowflake-managed environment over Remote-SSH.
The editor still runs on your laptop with your extensions, your keybindings, and your project layout. The environment it connects to (the kernel, the compute pool, the Python runtime, the Snowflake libraries) runs on Snowflake.
Snowflake Notebooks already run on Snowflake compute, close to the data, with the connecting role governing what they can reach. Remote Development extends that same model to your local editor. The governance, compute posture, and kernel are all the same; the surface is the only thing that changes.
How the connection works
Remote Development uses Microsoft's Remote-SSH extension (ms-vscode-remote.remote-ssh), which is how both VS Code and Cursor already handle remote hosts. The Snowflake extension does the setup:
- You pick a compute pool and options in the extension UI.
- Snowflake stands up a container service running an SSH endpoint.
- The extension writes the SSH config and reopens your editor into a remote window.
The extension handles SSH key generation, port forwarding, and jump host configuration for you. The extension is the same in VS Code and in Cursor because Cursor is a VS Code fork with the same extension model.
Inside the remote window, notebooks use the Snowflake Kernel (Python + SQL), the same kernel Snowflake Notebooks use when they run in Workspaces in the Snowsight UI. If you've worked in Jupyter you have muscle memory around this: activate a conda or miniconda env, register it with ipykernel, hunt for the right kernel in the picker, watch VS Code decide the wrong one is default, restart when a kernel dies. In a notebook that mixes Python and SQL, it gets worse. You either pick a Python kernel and wrap SQL in string cells, or pick a SQL kernel and lose Python's ecosystem.
The Snowflake Kernel folds that into one. Python and SQL cells run side by side in the same notebook. A SQL cell runs against Snowflake directly. A Python cell already has a live Snowpark session bound to it, so session.table('...') returns a DataFrame without any connection-setup boilerplate. The kernel handles the plumbing between the two languages for you.
Setting up your first Snowflake Remote Environment
-
Install or update the Snowflake extension in VS Code (or Cursor) to the latest version. Persistent storage and Git support require a recent release.
-
Install the Remote - SSH extension (
ms-vscode-remote.remote-ssh) from the Microsoft marketplace. -
Sign in to Snowflake from the extension and expand Remote Environments in the sidebar.
-
Select Create Remote Development Environment. Give it a name, pick workspaces to mount, choose a compute pool, and set Service settings, including Enable persistent storage if you want it.
-
When the service reaches
RUNNING, select Setup SSH. The editor opens a new window connected to the environment.
Remote Development is on by default via the ENABLE_NOTEBOOK_SERVICE_REMOTE_VS_CODE_ACCESS account parameter. An account admin can disable it, but there's no enrollment step to turn it on.
If your work needs outbound network access (package installs from a public index, pushes to a Git provider), attach an external access integration when you create the environment.

Choosing your compute and storage
When you create a Remote Development environment, you choose:
- The compute pool it runs on. Your role needs
USAGEon it, and the pool must allow theNOTEBOOKworkload type. - CPU or GPU, if your account has a GPU pool.
- The Snowflake Container Runtime version. The runtime ships with Python, Jupyter, and Snowflake libraries preinstalled.
- Which workspaces to mount. Each mounts at
/root/workspaces/<name>/. You can attach more workspaces later with Manage workspaces on remote. Note that these workspaces correspond to the Workspaces you have defined in Snowflake. At the moment, this feature only supports mounting private workspaces (not shared ones).
One decision is worth making at creation time and can't be changed later: persistent storage. If you enable it under Service settings, the environment gets a drive at /mnt/pd0 that survives suspend and resume. If you don't enable it at creation, you can't add it after, unless you recreate the service.
Working with production data from VS Code
Because the environment lives on Snowflake, your notebook queries Snowflake data in place. There's no extraction of data to a laptop.
from snowflake.snowpark.context import get_active_session session = get_active_session() raw = session.table('tb_101.ml.daily_sales_weather').to_pandas() raw.columns = [c.lower() for c in raw.columns]
The role you connected with governs what you can see. Masking policies, row access policies, and grants that apply everywhere else in Snowflake apply here too. For training data specifically, that means you aren't sampling down to what a laptop can hold, and you aren't standing up a separate copy to work around governance.
For heavier joins, push the work into SQL and pull the result back:
CREATE OR REPLACE TABLE tb_101.ml.daily_sales_weather AS WITH weather_by_city AS ( SELECT date_valid_std, UPPER(city_name) AS city_upper, AVG(avg_temperature_air_2m_f) AS avg_temperature_air_2m_f, AVG(tot_precipitation_in) AS tot_precipitation_in FROM frostbyte_weathersource.onpoint_id.history_day WHERE date_valid_std BETWEEN (SELECT MIN(date) FROM tb_101.ml.daily_sales) AND (SELECT MAX(date) FROM tb_101.ml.daily_sales) GROUP BY date_valid_std, UPPER(city_name) ) SELECT ds.date, ds.location_id, ds.city, ds.daily_sales, w.avg_temperature_air_2m_f AS temp_f, w.tot_precipitation_in * 25.4 AS precip_mm FROM tb_101.ml.daily_sales ds JOIN weather_by_city w ON w.date_valid_std = ds.date AND w.city_upper = UPPER(ds.city);
Because this is the Snowflake kernel, a SQL cell like that one and the Python cell that pulls the result into pandas are the same notebook.
Installing packages
The Container Runtime already includes the common ML stack. When you need something else, there are three ways in, and they aren't additive. Pick one per environment.
Snowflake's PyPI shared repository. snowflake.snowpark.pypi_shared_repository is a schema-level, RBAC-governed artifact repository. Package policies can restrict which packages resolve. This is the default choice if you don't have infrastructure of your own.
A customer-hosted artifact repository registered in Snowflake. If your organization already runs Nexus, JFrog, Azure DevOps Artifacts, GCP Artifact Registry, or AWS CodeArtifact, register that repository in Snowflake and point the environment at it. Package pinning and provenance stay in the platform your team already manages.
An external access integration to a package index. For a package index you can reach over the network (public PyPI, an internal mirror), attach an EAI to the environment. An external access integration is the Snowflake object that grants outbound network access to a specific set of hosts.
The catch: when the environment is configured to use a repository, that's where packages come from. The EAI path is an alternative to the repository path. Be sure to pick the sourcing model on the way in.
With persistent storage enabled, whatever you install lands on /mnt/pd0 and is still there next time you connect.
Working from a Git repository
Training code and notebooks belong in version control. With persistent storage on, the pattern is the one you'd expect:
cd /mnt/pd0 git clone https://github.com/your-org/your-repo.git
Add the folder to your workspace and the editor's Source Control view works the way it does locally. For a private repo, store the credential in a Snowflake secret and attach it to the environment. Don't write a token to disk in the container.
This allows your Snowflake data to stay on Snowflake, your code to stay in Git where your team expects it, and makes the editor the seam between them.
An end-to-end ML shape
Getting a training job onto real GPUs today usually means picking up one of a few external substrates. You spin up an EC2 instance with a Deep Learning AMI and hope the CUDA driver, the kernel headers, and your torch version line up. Or you submit a SageMaker Training Job and shuffle your data into S3 first. You stand up a Ray cluster on Kubernetes with a head node, a worker autoscaler, and Helm charts to maintain. You reach for a hosted notebook that ships a fixed environment. Each of these works. Each of them is also a substrate you now operate, sitting between your data and your training loop, with the data moving across it.
Remote Development starts you inside the substrate your data is already on. Because the environment runs inside Snowflake, the compute underneath is Snowflake Container Runtime, a managed containerized execution layer built for ML and data science workloads. Container Runtime lets you bring any Python package, framework, or compute shape without provisioning the underlying infrastructure yourself.
Ray is an open-source framework for distributed Python workloads that ML teams use to scale training and inference across a cluster without hand-writing MPI or thread-pooling code. Container Runtime is a Ray cluster enriched with Snowflake ML components: distributed data ingestion, feature engineering pipelines, distributed model training, and hyperparameter tuning APIs.
Once the data pull, the packages, and the code are all sorted, an actual training loop looks about like it would anywhere else. Load features, do a time-based split, fit the model, log it to the Model Registry:
from snowflake.ml.registry import Registry registry = Registry( session=session, database_name="TB_101", schema_name="ML", ) registry.log_model( model, model_name="tb_sales_forecaster", version_name="v1", sample_input_data=X_train.head(), comment=f"Tasty Bytes daily sales forecaster. Holdout MAPE={mape:.3f}.", target_platforms=["WAREHOUSE", "SNOWPARK_CONTAINER_SERVICES"], )
Nothing about that snippet is Remote-Development-specific, which is the point. Once the environment is up, the surface is your normal editor and the code is your normal ML code. What Remote Development changes is where the code runs and what data it reaches. The code itself stays the same.
CoCo is also available in the remote window through the Snowflake extension, so if you're used to it as a pair programmer, it comes into the remote session with you.
Try it in a Quickstart
The fastest way to get a feel for Remote Development is the official Quickstart. It walks through creating an environment, opening it over Remote-SSH, cloning a repo into persistent storage, and training an XGBoost sales forecaster over the Tasty Bytes dataset with Marketplace weather joined in. Everything in this post is exercised end-to-end.
Remote Development is in public preview and we're actively working on it. If you hit friction where the feature meets your workflow, we'd love to know about it. Get in contact with your account team and share your feedback. Happy building!
Disclaimer: This article contains forward-looking statements, including about our future product offerings, and are not commitments to deliver any product offerings. Actual results and offerings may differ and are subject to known and unknown risk and uncertainties. See our latest 10-Q for more information.
This content is provided as is, and is not maintained on an ongoing basis. It may be out of date with current Snowflake instances