Repository: skypilot-org/skypilot
Pull request #5892 (2025-06-05 16:07:17)

[SSH] Volume mounting for SSH Node Pools
Adds docs and plumbing for mounting host paths into SkyPilot jobs running on SSH node pools.

Future TODO: support this through a more native file_mounts? This is a very common JTBD for fixed clusters.


Patch:
diff --git a/.github/workflows/buildkite-trigger-wait.yml b/.github/workflows/buildkite-trigger-wait.yml
new file mode 100644
index 000000000..bf0dde293
--- /dev/null
+++ b/.github/workflows/buildkite-trigger-wait.yml
@@ -0,0 +1,91 @@
+name: Buildkite Trigger and Wait
+
+on:
+  workflow_call:
+    inputs:
+      commit:
+        required: true
+        type: string
+        description: "Commit SHA to trigger build for"
+      branch:
+        required: false
+        type: string
+        default: "master"
+        description: "Branch to trigger build for"
+      message:
+        required: true
+        type: string
+        description: "Message for the build"
+      pipeline:
+        required: true
+        type: string
+        description: "Buildkite pipeline name (e.g., smoke-tests)"
+      build_env_vars:
+        required: false
+        type: string
+        default: '{}'
+        description: "JSON string of environment variables to pass to the build"
+      timeout_minutes:
+        required: false
+        type: number
+        default: 120
+        description: "Timeout in minutes for waiting"
+      wait:
+        required: false
+        type: boolean
+        default: true
+        description: "Whether to wait for the build to complete"
+      fail_on_buildkite_failure:
+        required: false
+        type: boolean
+        default: true
+        description: "Whether to fail the job if the Buildkite build fails"
+    secrets:
+      BUILDKITE_TOKEN:
+        required: true
+        description: "Buildkite API token with read access"
+    outputs:
+      build_number:
+        description: "Buildkite build number"
+        value: ${{ jobs.extract-buildkite.outputs.build_number }}
+      build_status:
+        description: "Final status of the build (success, failure, timeout)"
+        value: ${{ jobs.wait-for-buildkite.outputs.build_status }}
+
+jobs:
+  trigger-buildkite:
+    runs-on: ubuntu-latest
+    outputs:
+      json: ${{ steps.trigger_buildkite.outputs.json }}
+    steps:
+      # Trigger Buildkite smoke tests
+      - name: Trigger Buildkite Smoke Tests
+        id: trigger_buildkite
+        uses: buildkite/trigger-pipeline-action@v2.3.0
+        with:
+          buildkite_api_access_token: ${{ secrets.BUILDKITE_TOKEN }}
+          pipeline: skypilot-1/${{ inputs.pipeline }}
+          branch: ${{ inputs.branch }}
+          commit: ${{ inputs.commit }}
+          message: ${{ inputs.message }}
+          ignore_pipeline_branch_filter: true
+          build_env_vars: ${{ inputs.build_env_vars }}
+
+  extract-buildkite:
+    needs: trigger-buildkite
+    uses: ./.github/workflows/extract-buildkite.yml
+    with:
+      json_data: ${{ needs.trigger-buildkite.outputs.json }}
+
+  wait-for-buildkite:
+    needs: extract-buildkite
+    if: ${{ inputs.wait }}
+    uses: ./.github/workflows/wait-for-buildkite.yml
+    with:
+      organization: "skypilot-1"
+      pipeline: ${{ inputs.pipeline }}
+      build_number: ${{ needs.extract-buildkite.outputs.build_number }}
+      timeout_minutes: ${{ inputs.timeout_minutes }}
+      fail_on_buildkite_failure: ${{ inputs.fail_on_buildkite_failure }}
+    secrets:
+      BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }}
diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml
index 2d13d2d40..1394faeaf 100644
--- a/.github/workflows/nightly-build.yml
+++ b/.github/workflows/nightly-build.yml
@@ -16,7 +16,6 @@ on:
         type: boolean
 
 jobs:
-  # nightly release check from https://stackoverflow.com/a/67527144
   check-date:
     runs-on: ubuntu-latest
     outputs:
@@ -36,7 +35,6 @@ jobs:
     needs: check-date
     if: ${{ needs.check-date.outputs.should_run != 'false' }}
     outputs:
-      buildkite_json: ${{ steps.trigger_buildkite.outputs.json }}
       version: ${{ steps.set-release-version.outputs.version }}
     steps:
       - name: Clone repository
@@ -89,62 +87,33 @@ jobs:
           name: python-package-distributions
           path: dist/
 
-      # Trigger Buildkite smoke tests
-      - name: Trigger Buildkite Smoke Tests
-        if: ${{ !inputs.skip_buildkite }}
-        id: trigger_buildkite
-        uses: buildkite/trigger-pipeline-action@v2.3.0
-        with:
-          buildkite_api_access_token: ${{ secrets.BUILDKITE_TOKEN }}
-          pipeline: "skypilot-1/smoke-tests"
-          branch: "master"
-          commit: "${{ github.sha }}"
-          message: "nightly-build-pypi"
-          ignore_pipeline_branch_filter: true
-          build_env_vars: '{"ARGS": "--aws"}'
-
-  extract-build-number:
+  smoke-tests:
     needs: nightly-build-pypi
-    uses: ./.github/workflows/extract-buildkite.yml
+    if: ${{ github.event_name == 'workflow_dispatch' && !inputs.skip_buildkite || github.event_name == 'schedule' }}
+    uses: ./.github/workflows/buildkite-trigger-wait.yml
     with:
-      json_data: ${{ needs.nightly-build-pypi.outputs.buildkite_json }}
-
-  wait-for-buildkite:
-    needs: extract-build-number
-    if: ${{ !inputs.skip_buildkite }}
-    uses: ./.github/workflows/wait-for-buildkite.yml
-    with:
-      organization: "skypilot-1"
+      commit: ${{ github.sha }}
+      branch: master
+      message: "nightly-build-pypi"
       pipeline: "smoke-tests"
-      build_number: ${{ needs.extract-build-number.outputs.build_number }}
+      build_env_vars: '{"ARGS": "--aws"}'
       timeout_minutes: 120
     secrets:
       BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }}
 
-  publish-and-validate-test-pypi:
-    needs: [nightly-build-pypi, wait-for-buildkite]
-    # Use `always()` to ensure this job runs even if the previous job are skipped.
-    if: ${{ !inputs.skip_test_pypi && (inputs.skip_buildkite || (always() && needs.wait-for-buildkite.result == 'success')) }}
-    uses: ./.github/workflows/publish-and-validate.yml
-    with:
-      package_name: skypilot-nightly
-      expected_version: ${{ needs.nightly-build-pypi.outputs.version }}
-      repository_type: 'test-pypi'
-    secrets: inherit
-
-  publish-and-validate-pypi:
-    needs: [nightly-build-pypi, publish-and-validate-test-pypi]
-    # Use `always()` to ensure this job runs even if the previous job are skipped.
-    if: ${{ inputs.skip_test_pypi || (always() && needs.publish-and-validate-test-pypi.result == 'success') }}
-    uses: ./.github/workflows/publish-and-validate.yml
+  publish-and-validate-both:
+    needs: [nightly-build-pypi, smoke-tests]
+    if: ${{ (github.event_name == 'workflow_dispatch' && inputs.skip_buildkite) || (always() && needs.smoke-tests.result == 'success') }}
+    uses: ./.github/workflows/publish-and-validate-both.yml
     with:
       package_name: skypilot-nightly
       expected_version: ${{ needs.nightly-build-pypi.outputs.version }}
-      repository_type: 'pypi'
+      skip_test_pypi: ${{ github.event_name == 'workflow_dispatch' && inputs.skip_test_pypi || false }}
     secrets: inherit
 
   trigger-helm-release:
-    needs: [publish-and-validate-pypi]
+    needs: [publish-and-validate-both]
+    if: ${{ needs.publish-and-validate-both.result == 'success' }}
     uses: ./.github/workflows/helm-docker-release.yaml
     with:
       package_name: skypilot-nightly
@@ -152,7 +121,7 @@ jobs:
 
   notify-slack-failure:
     runs-on: ubuntu-latest
-    needs: [check-date, nightly-build-pypi, extract-build-number, wait-for-buildkite, publish-and-validate-test-pypi, publish-and-validate-pypi, trigger-helm-release]
+    needs: [check-date, nightly-build-pypi, smoke-tests, publish-and-validate-both, trigger-helm-release]
     if: failure() # Only run this job if any of the previous jobs failed
     steps:
       - name: Prepare failure message
@@ -162,8 +131,8 @@ jobs:
           COMMIT_SHA="${{ github.sha }}"
           COMMIT_URL="${{ github.server_url }}/${{ github.repository }}/commit/${COMMIT_SHA}"
           SHORT_SHA=$(echo "$COMMIT_SHA" | cut -c1-7)
-          if [[ "${{ needs.wait-for-buildkite.result }}" == "failure" ]]; then
-            MARKDOWN_TEXT="🚨 Workflow <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|*${{ github.workflow }}*> failed at Buildkite step for commit <${COMMIT_URL}|${SHORT_SHA}>. <https://buildkite.com/skypilot-1/smoke-tests/builds/${{ needs.extract-build-number.outputs.build_number }}|Buildkite Log>"
+          if [[ "${{ needs.smoke-tests.result }}" == "failure" ]]; then
+            MARKDOWN_TEXT="🚨 Workflow <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|*${{ github.workflow }}*> failed at Buildkite step for commit <${COMMIT_URL}|${SHORT_SHA}>. <https://buildkite.com/skypilot-1/smoke-tests/builds/${{ needs.smoke-tests.outputs.build_number }}|Buildkite Log>"
             echo "message_text=${TITLE_TEXT}" >> $GITHUB_OUTPUT
             echo "message_block=${MARKDOWN_TEXT}" >> $GITHUB_OUTPUT
           else
diff --git a/.github/workflows/publish-and-validate-both.yml b/.github/workflows/publish-and-validate-both.yml
new file mode 100644
index 000000000..64816976c
--- /dev/null
+++ b/.github/workflows/publish-and-validate-both.yml
@@ -0,0 +1,45 @@
+name: Publish and Validate Package to Both Repositories
+
+on:
+  workflow_call:
+    inputs:
+      package_name:
+        description: 'Name of the package to validate (skypilot or skypilot-nightly)'
+        required: true
+        type: string
+      expected_version:
+        description: 'Expected version of the package'
+        required: true
+        type: string
+      skip_test_pypi:
+        description: 'Skip publishing to Test PyPI'
+        required: false
+        default: false
+        type: boolean
+    secrets:
+      TEST_PYPI_API_TOKEN:
+        description: 'API token for Test PyPI'
+        required: true
+      PYPI_API_TOKEN:
+        description: 'API token for PyPI'
+        required: true
+
+jobs:
+  publish-and-validate-test-pypi:
+    if: ${{ !inputs.skip_test_pypi }}
+    uses: ./.github/workflows/publish-and-validate.yml
+    with:
+      package_name: ${{ inputs.package_name }}
+      expected_version: ${{ inputs.expected_version }}
+      repository_type: 'test-pypi'
+    secrets: inherit
+
+  publish-and-validate-pypi:
+    needs: [publish-and-validate-test-pypi]
+    if: always()
+    uses: ./.github/workflows/publish-and-validate.yml
+    with:
+      package_name: ${{ inputs.package_name }}
+      expected_version: ${{ inputs.expected_version }}
+      repository_type: 'pypi'
+    secrets: inherit
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index bb699ced5..2e05313c3 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -20,11 +20,11 @@ jobs:
     runs-on: ubuntu-latest
     outputs:
       test_branch: ${{ steps.create_test_branch.outputs.test_branch }}
-      smoke_tests_json: ${{ steps.trigger_smoke_tests.outputs.json }}
-      quicktest_json: ${{ steps.trigger_quicktest_core.outputs.json }}
-      release_test_json: ${{ steps.trigger_release_tests.outputs.json }}
       release_branch: ${{ steps.create_release_branch.outputs.release_branch }}
       release_version: ${{ steps.determine_version.outputs.release_version }}
+      new_commit_sha: ${{ steps.create_test_branch.outputs.new_commit_sha }}
+      previous_release_branch: ${{ steps.find_previous_release_branch.outputs.previous_release_branch }}
+
     steps:
       - name: Clone repository
         uses: actions/checkout@v3
@@ -273,88 +273,50 @@ jobs:
           git push -f origin ${TEST_BRANCH}
           echo "test_branch=${TEST_BRANCH}" >> $GITHUB_OUTPUT
 
-      # Trigger Buildkite smoke tests
-      - name: Trigger Full Smoke Tests
-        id: trigger_smoke_tests
-        uses: buildkite/trigger-pipeline-action@v2.3.0
-        with:
-          buildkite_api_access_token: ${{ secrets.BUILDKITE_TOKEN }}
-          pipeline: "skypilot-1/full-smoke-tests-run"
-          branch: "${{ steps.create_test_branch.outputs.test_branch }}"
-          commit: "${{ steps.create_test_branch.outputs.new_commit_sha }}"
-          message: "Release ${{ steps.determine_version.outputs.release_version }}"
-          ignore_pipeline_branch_filter: true
-
-      # Trigger Buildkite quicktest-core
-      - name: Trigger Quicktest Core
-        id: trigger_quicktest_core
-        uses: buildkite/trigger-pipeline-action@v2.3.0
-        with:
-          buildkite_api_access_token: ${{ secrets.BUILDKITE_TOKEN }}
-          pipeline: "skypilot-1/quicktest-core"
-          branch: "${{ steps.create_test_branch.outputs.test_branch }}"
-          commit: "${{ steps.create_test_branch.outputs.new_commit_sha }}"
-          message: "Release ${{ steps.determine_version.outputs.release_version }}"
-          ignore_pipeline_branch_filter: true
-          build_env_vars: '{"ARGS": "--base-branch ${{ steps.find_previous_release_branch.outputs.previous_release_branch }}"}'
-
-      # Trigger Buildkite release tests
-      - name: Trigger Release Tests
-        id: trigger_release_tests
-        uses: buildkite/trigger-pipeline-action@v2.3.0
-        with:
-          buildkite_api_access_token: ${{ secrets.BUILDKITE_TOKEN }}
-          pipeline: "skypilot-1/release"
-          branch: "${{ steps.create_test_branch.outputs.test_branch }}"
-          commit: "${{ steps.create_test_branch.outputs.new_commit_sha }}"
-          message: "Release ${{ steps.determine_version.outputs.release_version }}"
-          ignore_pipeline_branch_filter: true
-
-  # Call extract-buildkite workflow for each job
-  extract-smoke-tests:
-    needs: release-build
-    uses: ./.github/workflows/extract-buildkite.yml
-    with:
-      json_data: ${{ needs.release-build.outputs.smoke_tests_json }}
-
-  extract-quicktest:
-    needs: release-build
-    uses: ./.github/workflows/extract-buildkite.yml
-    with:
-      json_data: ${{ needs.release-build.outputs.quicktest_json }}
-
-  extract-release-test:
+  smoke-tests:
     needs: release-build
-    uses: ./.github/workflows/extract-buildkite.yml
+    uses: ./.github/workflows/buildkite-trigger-wait.yml
     with:
-      json_data: ${{ needs.release-build.outputs.release_test_json }}
-
-  wait-for-smoke-tests:
-    needs: [release-build, extract-smoke-tests]
-    uses: ./.github/workflows/wait-for-buildkite.yml
-    with:
-      organization: "skypilot-1"
+      commit: ${{ needs.release-build.outputs.new_commit_sha }}
+      branch: ${{ needs.release-build.outputs.test_branch }}
+      message: "Release ${{ needs.release-build.outputs.release_version }}"
       pipeline: "full-smoke-tests-run"
-      build_number: ${{ needs.extract-smoke-tests.outputs.build_number }}
       timeout_minutes: 240
-      fail_on_buildkite_failure: false
+      wait: true
+      fail_on_buildkite_failure: true
     secrets:
       BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }}
 
-  wait-for-quicktest-core:
-    needs: [release-build, extract-quicktest]
-    uses: ./.github/workflows/wait-for-buildkite.yml
+  quicktest-core:
+    needs: release-build
+    uses: ./.github/workflows/buildkite-trigger-wait.yml
     with:
-      organization: "skypilot-1"
+      commit: ${{ needs.release-build.outputs.new_commit_sha }}
+      branch: ${{ needs.release-build.outputs.test_branch }}
+      message: "Release ${{ needs.release-build.outputs.release_version }}"
       pipeline: "quicktest-core"
-      build_number: ${{ needs.extract-quicktest.outputs.build_number }}
+      build_env_vars: '{"ARGS": "--base-branch ${{ needs.release-build.outputs.previous_release_branch }}"}'
       timeout_minutes: 180
+      wait: true
+      fail_on_buildkite_failure: true
+    secrets:
+      BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }}
+
+  release-tests:
+    needs: release-build
+    uses: ./.github/workflows/buildkite-trigger-wait.yml
+    with:
+      commit: ${{ needs.release-build.outputs.new_commit_sha }}
+      branch: ${{ needs.release-build.outputs.test_branch }}
+      message: "Release ${{ needs.release-build.outputs.release_version }}"
+      pipeline: "release"
+      wait: false
       fail_on_buildkite_failure: false
     secrets:
       BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }}
 
   create-pr:
-    needs: [release-build, wait-for-smoke-tests, wait-for-quicktest-core, extract-release-test, extract-smoke-tests, extract-quicktest]
+    needs: [release-build, smoke-tests, quicktest-core, release-tests]
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
@@ -368,11 +330,10 @@ jobs:
           TEST_BRANCH: ${{ needs.release-build.outputs.test_branch }}
           RELEASE_BRANCH: ${{ needs.release-build.outputs.release_branch }}
           RELEASE_VERSION: ${{ needs.release-build.outputs.release_version }}
-          SMOKE_TEST_BUILD: ${{ needs.extract-smoke-tests.outputs.build_number }}
-          QUICKTEST_BUILD: ${{ needs.extract-quicktest.outputs.build_number }}
-          RELEASE_TEST_BUILD: ${{ needs.extract-release-test.outputs.build_number }}
+          SMOKE_TEST_BUILD: ${{ needs.smoke-tests.outputs.build_number }}
+          QUICKTEST_BUILD: ${{ needs.quicktest-core.outputs.build_number }}
+          RELEASE_TEST_BUILD: ${{ needs.release-tests.outputs.build_number }}
         run: |
-
           # Configure git
           git config --local user.email "action@github.com"
           git config --local user.name "GitHub Action"
@@ -381,8 +342,8 @@ jobs:
           PR_BODY="Release ${RELEASE_VERSION}
 
           Buildkite Test Links:
-          - [Full Smoke Tests](https://buildkite.com/skypilot-1/full-smoke-tests-run/builds/${SMOKE_TEST_BUILD}) - $([ "${{ needs.wait-for-smoke-tests.outputs.build_status }}" == "success" ] && echo "✅ Success" || echo "❌ Failed")
-          - [Quicktest Core](https://buildkite.com/skypilot-1/quicktest-core/builds/${QUICKTEST_BUILD}) - $([ "${{ needs.wait-for-quicktest-core.outputs.build_status }}" == "success" ] && echo "✅ Success" || echo "❌ Failed")
+          - [Full Smoke Tests](https://buildkite.com/skypilot-1/full-smoke-tests-run/builds/${SMOKE_TEST_BUILD}) - $([ "${{ needs.smoke-tests.outputs.build_status }}" == "success" ] && echo "✅ Success" || echo "❌ Failed")
+          - [Quicktest Core](https://buildkite.com/skypilot-1/quicktest-core/builds/${QUICKTEST_BUILD}) - $([ "${{ needs.quicktest-core.outputs.build_status }}" == "success" ] && echo "✅ Success" || echo "❌ Failed")
           - [Release Tests](https://buildkite.com/skypilot-1/release/builds/${RELEASE_TEST_BUILD}) - ⏳ (not waiting for completion)
 
           *Release Tests may take up to 24 hours to complete and might fail due to resource constraints.*"
diff --git a/docs/source/reservations/existing-machines.rst b/docs/source/reservations/existing-machines.rst
index 5863b0e1f..4aad73c3a 100644
--- a/docs/source/reservations/existing-machines.rst
+++ b/docs/source/reservations/existing-machines.rst
@@ -203,6 +203,120 @@ SkyPilot automatically select one with available resources.
     # Let SkyPilot automatically select the cluster with available resources.
     sky launch --infra ssh -- echo "Running on SkyPilot selected cluster"
 
+Attaching NFS and other volumes
+-------------------------------
+
+SkyPilot jobs can access NFS, shared disks, or local high-performance storage like NVMe drives available on your host machines in a SSH Node Pool.
+
+Volume mounting can be done directly in the task YAML on a per-task basis, or globally for all tasks in :code:`~/.sky/config.yaml`.
+
+.. tab-set::
+
+    .. tab-item:: Mounting a path on the host (NFS)
+      :name: ssh-volumes-hostpath-nfs
+
+      Any path available on the host can be directly mounted to SkyPilot jobs. For example, to mount a NFS share available on the hosts:
+
+      **Per-task configuration:**
+
+      .. code-block:: yaml
+
+           # task.yaml
+           run: |
+             echo "Hello, world!" > /mnt/nfs/hello.txt
+             ls -la /mnt/nfs
+
+           config:
+             ssh:
+               pod_config:
+                 spec:
+                   containers:
+                     - volumeMounts:
+                         - mountPath: /mnt/nfs
+                           name: my-host-nfs
+                   volumes:
+                     - name: my-host-nfs
+                       hostPath:
+                         path: /path/on/host/nfs
+                         type: Directory
+
+      **Global configuration:**
+
+      .. code-block:: yaml
+
+           # ~/.sky/config.yaml
+           ssh:
+             pod_config:
+               spec:
+                 containers:
+                   - volumeMounts:
+                       - mountPath: /mnt/nfs
+                         name: my-host-nfs
+                 volumes:
+                   - name: my-host-nfs
+                     hostPath:
+                       path: /path/on/host/nfs
+                       type: Directory
+
+
+    .. tab-item:: Nebius shared filesystem
+      :name: ssh-volumes-nebius-shared-filesystem
+
+      SSH Node Pools running on Nebius VMs can access Nebius shared filesystems.
+
+      When creating a VM on the Nebius console, attach your desired shared file system to the VM (``Create virtual machine`` -> ``Attach shared filesystem``):
+
+      * Ensure ``Auto mount`` is enabled.
+      * Note the ``Mount tag`` (e.g. ``filesystem-d0``).
+
+      .. image:: ../images/screenshots/nebius/nebius-k8s-attach-fs.png
+        :width: 50%
+        :align: center
+
+      Nebius will automatically mount the shared filesystem to all hosts. You can then attach the volume to your SkyPilot jobs:
+
+      **Per-task configuration:**
+
+      .. code-block:: yaml
+
+           # task.yaml
+           run: |
+             echo "Hello, world!" > /mnt/nfs/hello.txt
+             ls -la /mnt/nfs
+
+           config:
+             ssh:
+               pod_config:
+                 spec:
+                   containers:
+                     - volumeMounts:
+                         - mountPath: /mnt/nfs
+                           name: nebius-sharedfs
+                   volumes:
+                     - name: nebius-sharedfs
+                       hostPath:
+                         path: /mnt/<mount_tag> # e.g. /mnt/filesystem-d0
+                         type: Directory
+
+      **Global configuration:**
+
+      .. code-block:: yaml
+
+           # ~/.sky/config.yaml
+           ssh:
+             pod_config:
+               spec:
+                 containers:
+                   - volumeMounts:
+                       - mountPath: /mnt/nfs
+                         name: nebius-sharedfs
+                 volumes:
+                   - name: nebius-sharedfs
+                     hostPath:
+                       path: /mnt/<mount_tag> # e.g. /mnt/filesystem-d0
+                       type: Directory
+
+
 Cleanup
 -------
 
diff --git a/examples/nebius_infiniband/README.md b/examples/nebius_infiniband/README.md
index e09ec57fa..d23351e75 100644
--- a/examples/nebius_infiniband/README.md
+++ b/examples/nebius_infiniband/README.md
@@ -158,28 +158,20 @@ While the previous section covered InfiniBand setup for managed Kubernetes servi
 
 SkyPilot simplifies the process of setting up InfiniBand-enabled GPU clusters on Nebius VMs. When you launch a cluster with the appropriate configurations, SkyPilot will automatically create a GPU cluster with InfiniBand support and add VMs to the GPU cluster.
 
-To enable automatic InfiniBand setup, you need to configure your `~/.sky/config.yaml` file with the following settings:
+To enable automatic InfiniBand setup, you can simply choose the best network in your SkyPilot YAML:
 
 ```yaml
-nebius:
-  eu-north1:
-    project_id: <project_id>
-    fabric: <fabric>
+resources:
+  network_tier: best
 ```
 
-Where:
-- `<project_id>`: Your Nebius project identifier
-- `<fabric>`: The GPU cluster configuration identifier that determines the InfiniBand fabric type
-
-For detailed information about fabric selection based on your GPU requirements, consult the [Nebius documentation](https://docs.nebius.com/compute/clusters/gpu#fabrics).
-
-Additional configuration options are available in the [SkyPilot Nebius configuration reference](https://docs.skypilot.co/en/latest/reference/config.html#nebius).
+SkyPilot will automatically configure the InfiniBand with the correct fabric for you. (Note that, Infiniband is only supported by two GPU types, H100:8 and H200:8. Refer to [Nebius Docs](https://docs.nebius.com/compute/clusters/gpu#fabrics)).
 
 ### Running Performance Tests
 
 You can verify your InfiniBand setup by running either of these tests:
 
-1. NCCL Performance Test:
+1. NCCL Performance Test (with specific docker image):
 
 ```bash
 sky launch -c infiniband nccl_vm_ib.yaml
@@ -187,20 +179,41 @@ sky launch -c infiniband nccl_vm_ib.yaml
 
 Result example:
 
+```
+#                                                              out-of-place                       in-place          
+#       size         count      type   redop    root     time   algbw   busbw #wrong     time   algbw   busbw #wrong
+#        (B)    (elements)                               (us)  (GB/s)  (GB/s)            (us)  (GB/s)  (GB/s)       
+   536870912     134217728     float     sum      -1   2399.1  223.78  419.59      0   2354.3  228.04  427.57      0
+  1073741824     268435456     float     sum      -1   4469.9  240.22  450.41      0   4463.1  240.58  451.09      0
+  2147483648     536870912     float     sum      -1   8678.7  247.44  463.96      0   8667.1  247.77  464.57      0
+  4294967296    1073741824     float     sum      -1    17053  251.86  472.24      0    17112  250.99  470.60      0
+  8589934592    2147483648     float     sum      -1    33792  254.20  476.62      0    33735  254.63  477.42      0
+# Out of bounds values : 0 OK
+# Avg bus bandwidth    : 457.407 
+```
+
+2. NCCL Performance Test (setting up NCCL):
+
+```bash
+sky launch -c infiniband nccl_no_docker_ib.yaml
+```
+
+Result example:
+
 ```
 #                                                              out-of-place                       in-place
-#       size         count      type   redop    root     time   algbw   busbw #wrong     time   algbw   busbw 
+#       size         count      type   redop    root     time   algbw   busbw #wrong     time   algbw   busbw #wrong
 #        (B)    (elements)                               (us)  (GB/s)  (GB/s)            (us)  (GB/s)  (GB/s)
-   536870912     134217728     float     sum      -1   2424.4  221.45  415.21      0   2391.0  224.53  421.00      
-  1073741824     268435456     float     sum      -1   4528.1  237.13  444.62      0   4533.5  236.85  444.09      
-  2147483648     536870912     float     sum      -1   8795.2  244.17  457.81      0   8783.6  244.49  458.42      
-  4294967296    1073741824     float     sum      -1    17442  246.25  461.71      0    17386  247.03  463.19      
-  8589934592    2147483648     float     sum      -1    34430  249.49  467.79      0    34443  249.39  467.61      
+   536870912     134217728     float     sum      -1   2378.7  225.70  423.19      0   2358.1  227.68  426.89      0
+  1073741824     268435456     float     sum      -1   4464.5  240.50  450.95      0   4461.2  240.69  451.29      0
+  2147483648     536870912     float     sum      -1   8697.7  246.90  462.94      0   8699.8  246.84  462.83      0
+  4294967296    1073741824     float     sum      -1    17406  246.75  462.66      0    17185  249.93  468.62      0
+  8589934592    2147483648     float     sum      -1    33782  254.28  476.77      0    33732  254.65  477.48      0
 # Out of bounds values : 0 OK
-# Avg bus bandwidth    : 450.146
+# Avg bus bandwidth    : 456.361
 ```
 
-2. InfiniBand Direct Test:
+3. InfiniBand Direct Test:
 
 ```bash
 sky launch -c infiniband infiniband.yaml
diff --git a/examples/nebius_infiniband/infiniband.yaml b/examples/nebius_infiniband/infiniband.yaml
index c195a56dd..5f2125fa1 100644
--- a/examples/nebius_infiniband/infiniband.yaml
+++ b/examples/nebius_infiniband/infiniband.yaml
@@ -4,6 +4,7 @@ resources:
   cloud: nebius
   region: eu-north1
   accelerators: H100:8
+  network_tier: best
   
 num_nodes: 2
 
diff --git a/examples/nebius_infiniband/nccl_no_docker_ib.yaml b/examples/nebius_infiniband/nccl_no_docker_ib.yaml
new file mode 100644
index 000000000..9dbcd43d3
--- /dev/null
+++ b/examples/nebius_infiniband/nccl_no_docker_ib.yaml
@@ -0,0 +1,138 @@
+# This example is used to test the NCCL performance
+# with InfiniBand on Nebius VMs.
+resources:
+  cloud: nebius
+  region: us-central1
+  accelerators: H200:8
+  network_tier: best
+
+num_nodes: 2
+
+setup: |
+  sudo apt-get update
+  sudo apt-get install -y iproute2 wget curl build-essential git
+
+  # Install OpenMPI first (required for NCCL tests)
+  echo "Installing OpenMPI..."
+  sudo apt-get install -y openmpi-bin openmpi-common libopenmpi-dev
+  
+  # Install CUDA toolkit
+  if ! command -v nvcc &> /dev/null; then
+    echo "Installing CUDA toolkit..."
+    wget https://developer.download.nvidia.com/compute/cuda/12.4.0/local_installers/cuda_12.4.0_550.54.14_linux.run
+    sudo sh cuda_12.4.0_550.54.14_linux.run --silent --toolkit
+    
+    # Add CUDA to PATH and LD_LIBRARY_PATH
+    echo 'export PATH=/usr/local/cuda/bin:$PATH' >> ~/.bashrc
+    echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
+    export PATH=/usr/local/cuda/bin:$PATH
+    export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
+  else
+    echo "CUDA already installed, skipping..."
+    export PATH=/usr/local/cuda/bin:$PATH
+    export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
+  fi
+
+  # Install NCCL (use sudo to remove if exists)
+  sudo rm -rf /usr/local/nccl
+  echo "Installing NCCL..."
+  cd /tmp
+  wget https://github.com/NVIDIA/nccl/archive/v2.23.4-1.tar.gz
+  tar -xzf v2.23.4-1.tar.gz
+  cd nccl-2.23.4-1
+  make -j src.build CUDA_HOME=/usr/local/cuda
+  sudo mkdir -p /usr/local/nccl
+  sudo cp -r build/* /usr/local/nccl/
+  echo 'export LD_LIBRARY_PATH=/usr/local/nccl/lib:$LD_LIBRARY_PATH' >> ~/.bashrc
+  export LD_LIBRARY_PATH=/usr/local/nccl/lib:$LD_LIBRARY_PATH
+
+  # Build NCCL tests
+  if [ ! -f $HOME/nccl-tests/build/all_reduce_perf ]; then
+    echo "Building NCCL tests..."
+    cd $HOME
+    git clone https://github.com/NVIDIA/nccl-tests.git
+    cd nccl-tests
+    
+    # Ensure environment variables are set for compilation
+    export PATH=/usr/local/cuda/bin:$PATH
+    export LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nccl/lib:$LD_LIBRARY_PATH
+    
+    # Get MPI compile flags from mpicc
+    MPI_CFLAGS=$(mpicc -showme:compile)
+    MPI_LDFLAGS=$(mpicc -showme:link)
+    MPI_HOME=$(dirname $(dirname $(which mpicc)))
+    
+    # Build with MPI flags from mpicc if not already built
+    make MPI=1 \
+        MPI_HOME="$MPI_HOME" \
+        CUDA_HOME=/usr/local/cuda \
+        NCCL_HOME=/usr/local/nccl \
+        CPPFLAGS="$MPI_CFLAGS" \
+        NVCCFLAGS="$MPI_CFLAGS" \
+        LDFLAGS="$MPI_LDFLAGS" \
+        -j
+  fi
+
+run: | 
+  # Load environment
+  export PATH=/usr/local/cuda/bin:$PATH
+  export LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nccl/lib:$LD_LIBRARY_PATH
+
+  export SSH_PORT=$(ss -tlnp | grep sshd | awk '{print $4}' | cut -d':' -f2)
+  export NCCL_SOCKET_IFNAME=$(ip -o -4 route show to default | awk '{print $5}')
+  export NCCL_IB_HCA=mlx5
+  export UCX_NET_DEVICES=mlx5_0:1,mlx5_1:1,mlx5_2:1,mlx5_3:1,mlx5_4:1,mlx5_5:1,mlx5_6:1,mlx5_7:1
+
+  # Debug SSH port detection
+  echo "=== SSH Port Detection Debug ==="
+  echo "ss -tlnp | grep sshd output:"
+  ss -tlnp | grep sshd
+  echo "awk '{print \$4}' output:"
+  ss -tlnp | grep sshd | awk '{print $4}'
+  echo "Final SSH_PORT: $SSH_PORT"
+  
+  # Fix SSH port detection
+  SSH_PORT_RAW=$(ss -tlnp | grep sshd | awk '{print $4}' | head -1)
+  export SSH_PORT=$(echo "$SSH_PORT_RAW" | sed 's/.*://')
+  if [ -z "$SSH_PORT" ]; then
+    export SSH_PORT=22
+  fi
+  echo "Corrected SSH_PORT: $SSH_PORT"
+  echo "================================="
+
+  # Total number of processes, NP should be the total number of GPUs in the cluster
+  NP=$(($SKYPILOT_NUM_GPUS_PER_NODE * $SKYPILOT_NUM_NODES))
+
+  # Create nodes list with slots for MPI
+  nodes=""
+  for ip in $SKYPILOT_NODE_IPS; do
+    nodes="${nodes}${ip}:${SKYPILOT_NUM_GPUS_PER_NODE},"
+  done
+  nodes=${nodes::-1}
+  echo "Node list: $nodes"
+  echo "Total processes: $NP"
+
+  if [ "${SKYPILOT_NODE_RANK}" == "0" ]; then
+    echo "Starting NCCL all-reduce test on ${SKYPILOT_NUM_NODES} nodes with ${NP} total GPUs..."
+    mpirun \
+      --allow-run-as-root \
+      -H $nodes \
+      -np $NP \
+      -N $SKYPILOT_NUM_GPUS_PER_NODE \
+      -bind-to none \
+      -x LD_LIBRARY_PATH \
+      -x NCCL_SOCKET_IFNAME \
+      -x NCCL_IB_HCA \
+      -x NCCL_ALGO=NVLSTree \
+      -x UCX_NET_DEVICES \
+      -x SHARP_COLL_ENABLE_PCI_RELAXED_ORDERING=1 \
+      -x NCCL_COLLNET_ENABLE=0 \
+      --mca plm_rsh_args "-p $SSH_PORT" \
+      $HOME/nccl-tests/build/all_reduce_perf \
+      -b 512M \
+      -e 8G \
+      -f 2 \
+      -g 1
+  else
+    echo "worker node"
+  fi
\ No newline at end of file
diff --git a/examples/nebius_infiniband/nccl_vm_ib.yaml b/examples/nebius_infiniband/nccl_vm_ib.yaml
index eff536c69..5ddbc0e8a 100644
--- a/examples/nebius_infiniband/nccl_vm_ib.yaml
+++ b/examples/nebius_infiniband/nccl_vm_ib.yaml
@@ -2,9 +2,10 @@
 # with InfiniBand on Nebius VMs.
 resources:
   cloud: nebius
-  region: eu-north1
+  region: us-central1
   accelerators: H100:8
   image_id: docker:cr.eu-north1.nebius.cloud/nebius-benchmarks/nccl-tests:2.23.4-ubu22.04-cu12.4
+  network_tier: best
 
 num_nodes: 2
 
@@ -35,9 +36,9 @@ run: |
       -bind-to none \
       -x LD_LIBRARY_PATH \
       -x NCCL_SOCKET_IFNAME \
-      -x NCCL_IB_HCA=mlx5 \
+      -x NCCL_IB_HCA \
       -x NCCL_ALGO=NVLSTree \
-      -x UCX_NET_DEVICES=mlx5_0:1,mlx5_1:1,mlx5_2:1,mlx5_3:1,mlx5_4:1,mlx5_5:1,mlx5_6:1,mlx5_7:1 \
+      -x UCX_NET_DEVICES \
       -x SHARP_COLL_ENABLE_PCI_RELAXED_ORDERING=1 \
       -x NCCL_COLLNET_ENABLE=0 \
       --mca plm_rsh_args "-p $SSH_PORT" \
@@ -48,10 +49,4 @@ run: |
       -g 1
   else
     echo "worker node"
-  fi
-
-config:
-  docker:
-    run_options:
-      - --device=/dev/infiniband
-      - --cap-add=IPC_LOCK
+  fi
\ No newline at end of file
diff --git a/sky/backends/backend_utils.py b/sky/backends/backend_utils.py
index 8e5638c80..a4cc9a444 100644
--- a/sky/backends/backend_utils.py
+++ b/sky/backends/backend_utils.py
@@ -815,7 +815,9 @@ def write_cluster_config(
     if isinstance(cloud, clouds.Kubernetes):
         cluster_config_overrides = to_provision.cluster_config_overrides
         kubernetes_utils.combine_pod_config_fields(
-            tmp_yaml_path, cluster_config_overrides=cluster_config_overrides)
+            tmp_yaml_path,
+            cluster_config_overrides=cluster_config_overrides,
+            cloud=cloud)
         kubernetes_utils.combine_metadata_fields(tmp_yaml_path)
         yaml_obj = common_utils.read_yaml(tmp_yaml_path)
         pod_config: Dict[str, Any] = yaml_obj['available_node_types'][
diff --git a/sky/cli.py b/sky/cli.py
index d404c8d8d..f0883640f 100644
--- a/sky/cli.py
+++ b/sky/cli.py
@@ -6056,7 +6056,6 @@ def api():
 
 
 @api.command('start', cls=_DocumentedCodeCommand)
-@config_option(expose_value=False)
 @click.option('--deploy',
               type=bool,
               is_flag=True,
@@ -6089,7 +6088,6 @@ def api_start(deploy: bool, host: Optional[str], foreground: bool):
 
 
 @api.command('stop', cls=_DocumentedCodeCommand)
-@config_option(expose_value=False)
 @usage_lib.entrypoint
 def api_stop():
     """Stops the SkyPilot API server locally."""
diff --git a/sky/clouds/cloud.py b/sky/clouds/cloud.py
index b85b037b6..caae8f462 100644
--- a/sky/clouds/cloud.py
+++ b/sky/clouds/cloud.py
@@ -726,10 +726,14 @@ class Cloud:
         """Errors out if the network tier is not supported by the
         cloud provider.
 
+        For BEST tier: always succeeds, will use best available tier.
+
         Raises:
             exceptions.NotSupportedError: If the network tier is not supported.
         """
         del instance_type  # unused
+
+        # For other tiers, check if supported
         if network_tier not in cls._SUPPORTED_NETWORK_TIERS:
             with ux_utils.print_exception_no_traceback():
                 raise exceptions.NotSupportedError(
diff --git a/sky/clouds/kubernetes.py b/sky/clouds/kubernetes.py
index 41a2be731..6225a68a6 100644
--- a/sky/clouds/kubernetes.py
+++ b/sky/clouds/kubernetes.py
@@ -188,8 +188,8 @@ class Kubernetes(clouds.Cloud):
             # Try kubeconfig if present
             current_context = (
                 kubernetes_utils.get_current_kube_config_context_name())
-            if (current_context is None and
-                    kubernetes_utils.is_incluster_config_available()):
+            if ((current_context is None or current_context.startswith('ssh-'))
+                    and kubernetes_utils.is_incluster_config_available()):
                 # If no kubeconfig contexts found, use in-cluster if available
                 current_context = kubernetes.in_cluster_context_name()
             allowed_contexts = []
diff --git a/sky/clouds/nebius.py b/sky/clouds/nebius.py
index 56a5702ae..0cdcdc911 100644
--- a/sky/clouds/nebius.py
+++ b/sky/clouds/nebius.py
@@ -9,6 +9,7 @@ from sky import clouds
 from sky import exceptions
 from sky import skypilot_config
 from sky.adaptors import nebius
+from sky.provision.nebius import constants as nebius_constants
 from sky.utils import annotations
 from sky.utils import registry
 from sky.utils import resources_utils
@@ -76,8 +77,20 @@ class Nebius(clouds.Cloud):
     def _unsupported_features_for_resources(
         cls, resources: 'resources_lib.Resources'
     ) -> Dict[clouds.CloudImplementationFeatures, str]:
-        del resources  # unused
-        return cls._CLOUD_UNSUPPORTED_FEATURES
+        unsupported = cls._CLOUD_UNSUPPORTED_FEATURES.copy()
+
+        # Check if the accelerators support InfiniBand (H100 or H200) and 8 GPUs
+        if resources.accelerators is not None:
+            for acc_name, acc_count in resources.accelerators.items():
+                if acc_name.lower() in ('h100', 'h200') and acc_count == 8:
+                    # Remove CUSTOM_NETWORK_TIER from unsupported features for
+                    # InfiniBand-capable accelerators
+                    unsupported.pop(
+                        clouds.CloudImplementationFeatures.CUSTOM_NETWORK_TIER,
+                        None)
+                    break
+
+        return unsupported
 
     @classmethod
     def _max_cluster_name_length(cls) -> Optional[int]:
@@ -227,14 +240,41 @@ class Nebius(clouds.Cloud):
             'image_id': image_family,
             # Nebius does not support specific zones.
             'zones': None,
-            'filesystems': resources_vars_fs
+            'filesystems': resources_vars_fs,
+            'network_tier': resources.network_tier
         }
 
+        docker_run_options = []
+
         if acc_dict is not None:
             # Nebius cloud's docker runtime information does not contain
             # 'nvidia-container-runtime', causing no GPU option to be added to
             # the docker run command. We patch this by adding it here.
-            resources_vars['docker_run_options'] = ['--gpus all']
+            docker_run_options.append('--gpus all')
+
+            # Check for InfiniBand support with network_tier: best
+            is_infiniband_capable = (
+                platform in nebius_constants.INFINIBAND_INSTANCE_PLATFORMS)
+            if (is_infiniband_capable and
+                    resources.network_tier == resources_utils.NetworkTier.BEST):
+                # For Docker containers, add InfiniBand device access and
+                # IPC_LOCK capability
+                if resources.extract_docker_image() is not None:
+                    docker_run_options.extend(
+                        nebius_constants.INFINIBAND_DOCKER_OPTIONS)
+
+                    # Add InfiniBand environment variables to docker run options
+                    for env_var, env_value in (
+                            nebius_constants.INFINIBAND_ENV_VARS.items()):
+                        docker_run_options.extend(
+                            ['-e', f'{env_var}={env_value}'])
+
+                # For all InfiniBand-capable instances, add env variables
+                resources_vars[
+                    'env_vars'] = nebius_constants.INFINIBAND_ENV_VARS
+
+        if docker_run_options:
+            resources_vars['docker_run_options'] = docker_run_options
 
         return resources_vars
 
diff --git a/sky/dashboard/src/components/jobs.jsx b/sky/dashboard/src/components/jobs.jsx
index 8b1af0671..ee04e8a53 100755
--- a/sky/dashboard/src/components/jobs.jsx
+++ b/sky/dashboard/src/components/jobs.jsx
@@ -354,8 +354,7 @@ export function ManagedJobsTable({
     setConfirmationModal({
       isOpen: true,
       title: 'Restart Controller',
-      message:
-        'Are you sure you want to restart the controller? This will temporarily interrupt job management.',
+      message: 'Are you sure you want to restart the controller?',
       onConfirm: async () => {
         try {
           setIsRestarting(true);
@@ -863,8 +862,8 @@ export function ManagedJobsTable({
                     {controllerLaunching && (
                       <div className="flex flex-col items-center space-y-2">
                         <p className="text-gray-700">
-                          The managed job controller is launching. Please wait
-                          for it to be ready.
+                          The managed job controller is launching. It will be
+                          ready shortly.
                         </p>
                         <div className="flex items-center">
                           <CircularProgress size={12} className="mr-2" />
@@ -878,8 +877,8 @@ export function ManagedJobsTable({
                     {controllerStopped && (
                       <div className="flex flex-col items-center space-y-2">
                         <p className="text-gray-700">
-                          The managed job controller has been stopped. Please
-                          restart it to check the latest job status.
+                          The managed job controller has been stopped. Restart
+                          to check the latest job status.
                         </p>
                         <Button
                           variant="outline"
diff --git a/sky/dashboard/src/pages/clusters/[cluster].js b/sky/dashboard/src/pages/clusters/[cluster].js
index 179e97457..190e5da4c 100644
--- a/sky/dashboard/src/pages/clusters/[cluster].js
+++ b/sky/dashboard/src/pages/clusters/[cluster].js
@@ -8,7 +8,13 @@ import { Status2Actions } from '@/components/clusters';
 import { StatusBadge } from '@/components/elements/StatusBadge';
 import { Card } from '@/components/ui/card';
 import { useClusterDetails } from '@/data/connectors/clusters';
-import { RotateCwIcon, ChevronDownIcon, ChevronRightIcon } from 'lucide-react';
+import {
+  RotateCwIcon,
+  ChevronDownIcon,
+  ChevronRightIcon,
+  CopyIcon,
+  CheckIcon,
+} from 'lucide-react';
 import yaml from 'js-yaml';
 import {
   CustomTooltip as Tooltip,
@@ -186,11 +192,34 @@ function ActiveTab({
   refreshClusterJobsOnly,
 }) {
   const [isYamlExpanded, setIsYamlExpanded] = useState(false);
+  const [isCopied, setIsCopied] = useState(false);
+  const [isCommandCopied, setIsCommandCopied] = useState(false);
 
   const toggleYamlExpanded = () => {
     setIsYamlExpanded(!isYamlExpanded);
   };
 
+  const copyYamlToClipboard = async () => {
+    try {
+      const formattedYaml = formatYaml(clusterData.task_yaml);
+      await navigator.clipboard.writeText(formattedYaml);
+      setIsCopied(true);
+      setTimeout(() => setIsCopied(false), 2000); // Reset after 2 seconds
+    } catch (err) {
+      console.error('Failed to copy YAML to clipboard:', err);
+    }
+  };
+
+  const copyCommandToClipboard = async () => {
+    try {
+      await navigator.clipboard.writeText(clusterData.command);
+      setIsCommandCopied(true);
+      setTimeout(() => setIsCommandCopied(false), 2000); // Reset after 2 seconds
+    } catch (err) {
+      console.error('Failed to copy command to clipboard:', err);
+    }
+  };
+
   const formatYaml = (yamlString) => {
     if (!yamlString) return 'No YAML available';
 
@@ -331,8 +360,27 @@ function ActiveTab({
               {/* Created by section - spans both columns */}
               {hasCreationArtifacts && (
                 <div className="col-span-2">
-                  <div className="text-gray-600 font-medium text-base">
-                    Entrypoint
+                  <div className="flex items-center">
+                    <div className="text-gray-600 font-medium text-base">
+                      Entrypoint
+                    </div>
+                    {clusterData.command && (
+                      <Tooltip
+                        content={isCommandCopied ? 'Copied!' : 'Copy command'}
+                        className="text-muted-foreground"
+                      >
+                        <button
+                          onClick={copyCommandToClipboard}
+                          className="flex items-center text-gray-500 hover:text-gray-700 transition-colors duration-200 p-1 ml-2"
+                        >
+                          {isCommandCopied ? (
+                            <CheckIcon className="w-4 h-4 text-green-600" />
+                          ) : (
+                            <CopyIcon className="w-4 h-4" />
+                          )}
+                        </button>
+                      </Tooltip>
+                    )}
                   </div>
 
                   <div className="space-y-4 mt-3">
@@ -349,13 +397,17 @@ function ActiveTab({
 
                     {/* Task YAML - Collapsible */}
                     {clusterData.task_yaml &&
-                      clusterData.task_yaml !== '{}' && (
+                      clusterData.task_yaml !== '{}' &&
+                      !clusterData.cluster.startsWith('sky-jobs-controller-') &&
+                      !clusterData.cluster.startsWith(
+                        'sky-serve-controller-'
+                      ) && (
                         <div>
-                          <button
-                            onClick={toggleYamlExpanded}
-                            className="flex items-center text-left focus:outline-none mb-2 text-gray-700 hover:text-gray-900 transition-colors duration-200"
-                          >
-                            <div className="flex items-center">
+                          <div className="flex items-center mb-2">
+                            <button
+                              onClick={toggleYamlExpanded}
+                              className="flex items-center text-left focus:outline-none text-gray-700 hover:text-gray-900 transition-colors duration-200"
+                            >
                               {isYamlExpanded ? (
                                 <ChevronDownIcon className="w-4 h-4 mr-1" />
                               ) : (
@@ -364,8 +416,24 @@ function ActiveTab({
                               <span className="text-base">
                                 Show SkyPilot YAML
                               </span>
-                            </div>
-                          </button>
+                            </button>
+
+                            <Tooltip
+                              content={isCopied ? 'Copied!' : 'Copy YAML'}
+                              className="text-muted-foreground"
+                            >
+                              <button
+                                onClick={copyYamlToClipboard}
+                                className="flex items-center text-gray-500 hover:text-gray-700 transition-colors duration-200 p-1 ml-2"
+                              >
+                                {isCopied ? (
+                                  <CheckIcon className="w-4 h-4 text-green-600" />
+                                ) : (
+                                  <CopyIcon className="w-4 h-4" />
+                                )}
+                              </button>
+                            </Tooltip>
+                          </div>
 
                           {isYamlExpanded && (
                             <div className="bg-gray-50 border border-gray-200 rounded-md p-3 max-h-96 overflow-y-auto">
diff --git a/sky/dashboard/src/pages/jobs/[job].js b/sky/dashboard/src/pages/jobs/[job].js
index 92ef670d6..480157ad6 100755
--- a/sky/dashboard/src/pages/jobs/[job].js
+++ b/sky/dashboard/src/pages/jobs/[job].js
@@ -5,7 +5,13 @@ import { Layout } from '@/components/elements/layout';
 import { Card } from '@/components/ui/card';
 import { useSingleManagedJob } from '@/data/connectors/jobs';
 import Link from 'next/link';
-import { RotateCwIcon, ChevronDownIcon, ChevronRightIcon } from 'lucide-react';
+import {
+  RotateCwIcon,
+  ChevronDownIcon,
+  ChevronRightIcon,
+  CopyIcon,
+  CheckIcon,
+} from 'lucide-react';
 import { CustomTooltip as Tooltip } from '@/components/utils';
 import { LogFilter, formatLogs } from '@/components/utils';
 import { streamManagedJobLogs } from '@/data/connectors/jobs';
@@ -314,6 +320,8 @@ function JobDetailsContent({
   const [controllerLogs, setControllerLogs] = useState('');
   const [isYamlExpanded, setIsYamlExpanded] = useState(false);
   const [expandedYamlDocs, setExpandedYamlDocs] = useState({});
+  const [isCopied, setIsCopied] = useState(false);
+  const [isCommandCopied, setIsCommandCopied] = useState(false);
 
   // Add state for tracking incremental refresh
   const [currentLogLength, setCurrentLogLength] = useState(0);
@@ -389,6 +397,40 @@ function JobDetailsContent({
     }));
   };
 
+  const copyYamlToClipboard = async () => {
+    try {
+      const yamlDocs = formatYaml(jobData.dag_yaml);
+      let textToCopy = '';
+
+      if (yamlDocs.length === 1) {
+        // Single document - use the formatted content directly
+        textToCopy = yamlDocs[0].content;
+      } else if (yamlDocs.length > 1) {
+        // Multiple documents - join them with document separators
+        textToCopy = yamlDocs.map((doc) => doc.content).join('\n---\n');
+      } else {
+        // Fallback to raw YAML if formatting fails
+        textToCopy = jobData.dag_yaml;
+      }
+
+      await navigator.clipboard.writeText(textToCopy);
+      setIsCopied(true);
+      setTimeout(() => setIsCopied(false), 2000); // Reset after 2 seconds
+    } catch (err) {
+      console.error('Failed to copy YAML to clipboard:', err);
+    }
+  };
+
+  const copyCommandToClipboard = async () => {
+    try {
+      await navigator.clipboard.writeText(jobData.entrypoint);
+      setIsCommandCopied(true);
+      setTimeout(() => setIsCommandCopied(false), 2000); // Reset after 2 seconds
+    } catch (err) {
+      console.error('Failed to copy command to clipboard:', err);
+    }
+  };
+
   const formatYaml = (yamlString) => {
     if (!yamlString) return [];
 
@@ -980,7 +1022,28 @@ function JobDetailsContent({
       {/* Entrypoint section - spans both columns */}
       {(jobData.entrypoint || jobData.dag_yaml) && (
         <div className="col-span-2">
-          <div className="text-gray-600 font-medium text-base">Entrypoint</div>
+          <div className="flex items-center">
+            <div className="text-gray-600 font-medium text-base">
+              Entrypoint
+            </div>
+            {jobData.entrypoint && (
+              <Tooltip
+                content={isCommandCopied ? 'Copied!' : 'Copy command'}
+                className="text-muted-foreground"
+              >
+                <button
+                  onClick={copyCommandToClipboard}
+                  className="flex items-center text-gray-500 hover:text-gray-700 transition-colors duration-200 p-1 ml-2"
+                >
+                  {isCommandCopied ? (
+                    <CheckIcon className="w-4 h-4 text-green-600" />
+                  ) : (
+                    <CopyIcon className="w-4 h-4" />
+                  )}
+                </button>
+              </Tooltip>
+            )}
+          </div>
 
           <div className="space-y-4 mt-3">
             {/* Launch Command */}
@@ -997,19 +1060,35 @@ function JobDetailsContent({
             {/* Task YAML - Collapsible */}
             {jobData.dag_yaml && jobData.dag_yaml !== '{}' && (
               <div>
-                <button
-                  onClick={toggleYamlExpanded}
-                  className="flex items-center text-left focus:outline-none mb-2 text-gray-700 hover:text-gray-900 transition-colors duration-200"
-                >
-                  <div className="flex items-center">
+                <div className="flex items-center mb-2">
+                  <button
+                    onClick={toggleYamlExpanded}
+                    className="flex items-center text-left focus:outline-none text-gray-700 hover:text-gray-900 transition-colors duration-200"
+                  >
                     {isYamlExpanded ? (
                       <ChevronDownIcon className="w-4 h-4 mr-1" />
                     ) : (
                       <ChevronRightIcon className="w-4 h-4 mr-1" />
                     )}
                     <span className="text-base">Show SkyPilot YAML</span>
-                  </div>
-                </button>
+                  </button>
+
+                  <Tooltip
+                    content={isCopied ? 'Copied!' : 'Copy YAML'}
+                    className="text-muted-foreground"
+                  >
+                    <button
+                      onClick={copyYamlToClipboard}
+                      className="flex items-center text-gray-500 hover:text-gray-700 transition-colors duration-200 p-1 ml-2"
+                    >
+                      {isCopied ? (
+                        <CheckIcon className="w-4 h-4 text-green-600" />
+                      ) : (
+                        <CopyIcon className="w-4 h-4" />
+                      )}
+                    </button>
+                  </Tooltip>
+                </div>
 
                 {isYamlExpanded && (
                   <div className="bg-gray-50 border border-gray-200 rounded-md p-3 max-h-96 overflow-y-auto">
diff --git a/sky/provision/kubernetes/utils.py b/sky/provision/kubernetes/utils.py
index 7779f33a9..0b792bfaa 100644
--- a/sky/provision/kubernetes/utils.py
+++ b/sky/provision/kubernetes/utils.py
@@ -2342,6 +2342,7 @@ def get_endpoint_debug_message() -> str:
 def combine_pod_config_fields(
     cluster_yaml_path: str,
     cluster_config_overrides: Dict[str, Any],
+    cloud: Optional[clouds.Cloud] = None,
 ) -> None:
     """Adds or updates fields in the YAML with fields from the
     ~/.sky/config.yaml's kubernetes.pod_spec dict.
@@ -2386,11 +2387,17 @@ def combine_pod_config_fields(
     yaml_obj = yaml.safe_load(yaml_content)
     # We don't use override_configs in `skypilot_config.get_nested`, as merging
     # the pod config requires special handling.
-    kubernetes_config = skypilot_config.get_nested(('kubernetes', 'pod_config'),
-                                                   default_value={},
-                                                   override_configs={})
-    override_pod_config = (cluster_config_overrides.get('kubernetes', {}).get(
-        'pod_config', {}))
+    if isinstance(cloud, clouds.SSH):
+        kubernetes_config = skypilot_config.get_nested(('ssh', 'pod_config'),
+                                                       default_value={},
+                                                       override_configs={})
+        override_pod_config = (cluster_config_overrides.get('ssh', {}).get(
+            'pod_config', {}))
+    else:
+        kubernetes_config = skypilot_config.get_nested(
+            ('kubernetes', 'pod_config'), default_value={}, override_configs={})
+        override_pod_config = (cluster_config_overrides.get(
+            'kubernetes', {}).get('pod_config', {}))
     config_utils.merge_k8s_configs(kubernetes_config, override_pod_config)
 
     # Merge the kubernetes config into the YAML for both head and worker nodes.
diff --git a/sky/provision/nebius/constants.py b/sky/provision/nebius/constants.py
new file mode 100644
index 000000000..816f7fa45
--- /dev/null
+++ b/sky/provision/nebius/constants.py
@@ -0,0 +1,47 @@
+"""Constants used by the Nebius provisioner."""
+
+VERSION = 'v1'
+
+# InfiniBand-capable instance platforms
+INFINIBAND_INSTANCE_PLATFORMS = [
+    'gpu-h100-sxm',
+    'gpu-h200-sxm',
+]
+
+# InfiniBand environment variables for NCCL and UCX
+INFINIBAND_ENV_VARS = {
+    'NCCL_IB_HCA': 'mlx5',
+    'UCX_NET_DEVICES': ('mlx5_0:1,mlx5_1:1,mlx5_2:1,mlx5_3:1,'
+                        'mlx5_4:1,mlx5_5:1,mlx5_6:1,mlx5_7:1')
+}
+
+# Docker run options for InfiniBand support
+INFINIBAND_DOCKER_OPTIONS = ['--device=/dev/infiniband', '--cap-add=IPC_LOCK']
+
+# InfiniBand fabric mapping by platform and region
+# Based on Nebius documentation
+INFINIBAND_FABRIC_MAPPING = {
+    # H100 platforms
+    ('gpu-h100-sxm', 'eu-north1'): [
+        'fabric-2', 'fabric-3', 'fabric-4', 'fabric-6'
+    ],
+
+    # H200 platforms
+    ('gpu-h200-sxm', 'eu-north1'): ['fabric-7'],
+    ('gpu-h200-sxm', 'eu-west1'): ['fabric-5'],
+    ('gpu-h200-sxm', 'us-central1'): ['us-central1-a'],
+}
+
+
+def get_default_fabric(platform: str, region: str) -> str:
+    """Get the default (first) fabric for a given platform and region."""
+    fabrics = INFINIBAND_FABRIC_MAPPING.get((platform, region), [])
+    if not fabrics:
+        # Select north europe region as default
+        fabrics = INFINIBAND_FABRIC_MAPPING.get(('gpu-h100-sxm', 'eu-north1'),
+                                                [])
+        if not fabrics:
+            raise ValueError(
+                f'No InfiniBand fabric available for platform {platform} '
+                f'in region {region}')
+    return fabrics[0]
diff --git a/sky/provision/nebius/instance.py b/sky/provision/nebius/instance.py
index d654ceaa0..0c4ebeca5 100644
--- a/sky/provision/nebius/instance.py
+++ b/sky/provision/nebius/instance.py
@@ -124,6 +124,7 @@ def run_instances(region: str, cluster_name_on_cloud: str,
         node_type = 'head' if head_instance_id is None else 'worker'
         try:
             platform, preset = config.node_config['InstanceType'].split('_')
+
             instance_id = utils.launch(
                 cluster_name_on_cloud=cluster_name_on_cloud,
                 node_type=node_type,
@@ -136,7 +137,7 @@ def run_instances(region: str, cluster_name_on_cloud: str,
                 associate_public_ip_address=(
                     not config.provider_config['use_internal_ips']),
                 filesystems=config.node_config.get('filesystems', []),
-            )
+                network_tier=config.node_config.get('network_tier'))
         except Exception as e:  # pylint: disable=broad-except
             logger.warning(f'run_instances error: {e}')
             raise
diff --git a/sky/provision/nebius/utils.py b/sky/provision/nebius/utils.py
index 7509dd10e..9b6b3444d 100644
--- a/sky/provision/nebius/utils.py
+++ b/sky/provision/nebius/utils.py
@@ -1,12 +1,14 @@
 """Nebius library wrapper for SkyPilot."""
 import time
-from typing import Any, Dict, List
+from typing import Any, Dict, List, Optional
 import uuid
 
 from sky import sky_logging
 from sky import skypilot_config
 from sky.adaptors import nebius
+from sky.provision.nebius import constants as nebius_constants
 from sky.utils import common_utils
+from sky.utils import resources_utils
 
 logger = sky_logging.init_logger(__name__)
 
@@ -156,10 +158,17 @@ def start(instance_id: str) -> None:
             f' to be ready.')
 
 
-def launch(cluster_name_on_cloud: str, node_type: str, platform: str,
-           preset: str, region: str, image_family: str, disk_size: int,
-           user_data: str, associate_public_ip_address: bool,
-           filesystems: List[Dict[str, Any]]) -> str:
+def launch(cluster_name_on_cloud: str,
+           node_type: str,
+           platform: str,
+           preset: str,
+           region: str,
+           image_family: str,
+           disk_size: int,
+           user_data: str,
+           associate_public_ip_address: bool,
+           filesystems: List[Dict[str, Any]],
+           network_tier: Optional[resources_utils.NetworkTier] = None) -> str:
     # Each node must have a unique name to avoid conflicts between
     # multiple worker VMs. To ensure uniqueness,a UUID is appended
     # to the node name.
@@ -173,11 +182,23 @@ def launch(cluster_name_on_cloud: str, node_type: str, platform: str,
     # 8 GPU virtual machines can be grouped into a GPU cluster.
     # The GPU clusters are built with InfiniBand secure high-speed networking.
     # https://docs.nebius.com/compute/clusters/gpu
-    if platform in ('gpu-h100-sxm', 'gpu-h200-sxm'):
+    if platform in nebius_constants.INFINIBAND_INSTANCE_PLATFORMS:
         if preset == '8gpu-128vcpu-1600gb':
-            #  Check is there fabric in config
             fabric = skypilot_config.get_nested(('nebius', region, 'fabric'),
                                                 None)
+
+            # Auto-select fabric if network_tier=best and no fabric configured
+            if (fabric is None and
+                    str(network_tier) == str(resources_utils.NetworkTier.BEST)):
+                try:
+                    fabric = nebius_constants.get_default_fabric(
+                        platform, region)
+                    logger.info(f'Auto-selected InfiniBand fabric {fabric} '
+                                f'for {platform} in {region}')
+                except ValueError as e:
+                    logger.warning(
+                        f'InfiniBand fabric auto-selection failed: {e}')
+
             if fabric is None:
                 logger.warning(
                     f'Set up fabric for region {region} in ~/.sky/config.yaml '
diff --git a/sky/serve/load_balancer.py b/sky/serve/load_balancer.py
index ecceea4ce..c319a2c84 100644
--- a/sky/serve/load_balancer.py
+++ b/sky/serve/load_balancer.py
@@ -2,7 +2,8 @@
 import asyncio
 import logging
 import threading
-from typing import Dict, Optional, Union
+import traceback
+from typing import Dict, List, Optional, Union
 
 import aiohttp
 import fastapi
@@ -69,6 +70,48 @@ class SkyServeLoadBalancer:
         # updating it from _sync_with_controller.
         self._client_pool_lock: threading.Lock = threading.Lock()
 
+    async def _sync_with_controller_once(self) -> List[asyncio.Task]:
+        close_client_tasks = []
+        async with aiohttp.ClientSession() as session:
+            try:
+                # Send request information
+                async with session.post(
+                        self._controller_url + '/controller/load_balancer_sync',
+                        json={
+                            'request_aggregator':
+                                self._request_aggregator.to_dict()
+                        },
+                        timeout=aiohttp.ClientTimeout(5),
+                ) as response:
+                    # Clean up after reporting request info to avoid OOM.
+                    self._request_aggregator.clear()
+                    response.raise_for_status()
+                    response_json = await response.json()
+                    ready_replica_urls = response_json.get(
+                        'ready_replica_urls', [])
+            except (aiohttp.ClientError, asyncio.TimeoutError) as e:
+                logger.error(f'An error occurred when syncing with '
+                             f'the controller: {e}'
+                             f'\nTraceback: {traceback.format_exc()}')
+            else:
+                logger.info(f'Available Replica URLs: {ready_replica_urls}')
+                with self._client_pool_lock:
+                    self._load_balancing_policy.set_ready_replicas(
+                        ready_replica_urls)
+                    for replica_url in ready_replica_urls:
+                        if replica_url not in self._client_pool:
+                            self._client_pool[replica_url] = httpx.AsyncClient(
+                                base_url=replica_url)
+                    urls_to_close = set(
+                        self._client_pool.keys()) - set(ready_replica_urls)
+                    client_to_close = []
+                    for replica_url in urls_to_close:
+                        client_to_close.append(
+                            self._client_pool.pop(replica_url))
+                for client in client_to_close:
+                    close_client_tasks.append(client.aclose())
+        return close_client_tasks
+
     async def _sync_with_controller(self):
         """Sync with controller periodically.
 
@@ -82,49 +125,16 @@ class SkyServeLoadBalancer:
         await asyncio.sleep(5)
 
         while True:
-            close_client_tasks = []
-            async with aiohttp.ClientSession() as session:
-                try:
-                    # Send request information
-                    async with session.post(
-                            self._controller_url +
-                            '/controller/load_balancer_sync',
-                            json={
-                                'request_aggregator':
-                                    self._request_aggregator.to_dict()
-                            },
-                            timeout=aiohttp.ClientTimeout(5),
-                    ) as response:
-                        # Clean up after reporting request info to avoid OOM.
-                        self._request_aggregator.clear()
-                        response.raise_for_status()
-                        response_json = await response.json()
-                        ready_replica_urls = response_json.get(
-                            'ready_replica_urls', [])
-                except aiohttp.ClientError as e:
-                    logger.error('An error occurred when syncing with '
-                                 f'the controller: {e}')
-                else:
-                    logger.info(f'Available Replica URLs: {ready_replica_urls}')
-                    with self._client_pool_lock:
-                        self._load_balancing_policy.set_ready_replicas(
-                            ready_replica_urls)
-                        for replica_url in ready_replica_urls:
-                            if replica_url not in self._client_pool:
-                                self._client_pool[replica_url] = (
-                                    httpx.AsyncClient(base_url=replica_url))
-                        urls_to_close = set(
-                            self._client_pool.keys()) - set(ready_replica_urls)
-                        client_to_close = []
-                        for replica_url in urls_to_close:
-                            client_to_close.append(
-                                self._client_pool.pop(replica_url))
-                    for client in client_to_close:
-                        close_client_tasks.append(client.aclose())
-
-            await asyncio.sleep(constants.LB_CONTROLLER_SYNC_INTERVAL_SECONDS)
-            # Await those tasks after the interval to avoid blocking.
-            await asyncio.gather(*close_client_tasks)
+            try:
+                close_client_tasks = await self._sync_with_controller_once()
+                await asyncio.sleep(
+                    constants.LB_CONTROLLER_SYNC_INTERVAL_SECONDS)
+                # Await those tasks after the interval to avoid blocking.
+                await asyncio.gather(*close_client_tasks)
+            except Exception as e:  # pylint: disable=broad-except
+                logger.error(f'An error occurred when syncing with '
+                             f'the controller: {e}'
+                             f'\nTraceback: {traceback.format_exc()}')
 
     async def _proxy_request_to(
         self, url: str, request: fastapi.Request
@@ -168,7 +178,8 @@ class SkyServeLoadBalancer:
                 background=background.BackgroundTask(background_func))
         except (httpx.RequestError, httpx.HTTPStatusError) as e:
             logger.error(f'Error when proxy request to {url}: '
-                         f'{common_utils.format_exception(e)}')
+                         f'{common_utils.format_exception(e)}'
+                         f'\nTraceback: {traceback.format_exc()}')
             return e
 
     async def _proxy_with_retries(
diff --git a/sky/skylet/constants.py b/sky/skylet/constants.py
index d5ad530fe..cc56c05f0 100644
--- a/sky/skylet/constants.py
+++ b/sky/skylet/constants.py
@@ -367,6 +367,7 @@ RCLONE_CACHE_REFRESH_INTERVAL = 10
 OVERRIDEABLE_CONFIG_KEYS_IN_TASK: List[Tuple[str, ...]] = [
     ('docker', 'run_options'),
     ('nvidia_gpus', 'disable_ecc'),
+    ('ssh', 'pod_config'),
     ('kubernetes', 'pod_config'),
     ('kubernetes', 'provision_timeout'),
     ('gcp', 'managed_instance_group'),
diff --git a/sky/templates/nebius-ray.yml.j2 b/sky/templates/nebius-ray.yml.j2
index 4961d2f6e..dafda0c6d 100644
--- a/sky/templates/nebius-ray.yml.j2
+++ b/sky/templates/nebius-ray.yml.j2
@@ -46,6 +46,7 @@ available_node_types:
       InstanceType: {{instance_type}}
       ImageId: {{image_id}}
       DiskSize: {{disk_size}}
+      network_tier: {{network_tier}}
       filesystems:
         {%- for fs in filesystems %}
         - filesystem_id: {{ fs.filesystem_id }}
@@ -152,6 +153,11 @@ setup_commands:
     mkdir -p ~/.ssh; touch ~/.ssh/config;
     {{ conda_installation_commands }}
     {{ ray_skypilot_installation_commands }}
+  {%- if env_vars is defined %}
+    {%- for env_var, env_value in env_vars.items() %}
+    echo '{{env_var}}={{env_value}}' | sudo tee -a /etc/environment;
+    {%- endfor %}
+  {%- endif %}
     sudo bash -c 'rm -rf /etc/security/limits.d; echo "* soft nofile 1048576" >> /etc/security/limits.conf; echo "* hard nofile 1048576" >> /etc/security/limits.conf';
     sudo grep -e '^DefaultTasksMax' /etc/systemd/system.conf || (sudo bash -c 'echo "DefaultTasksMax=infinity" >> /etc/systemd/system.conf'); sudo systemctl set-property user-$(id -u $(whoami)).slice TasksMax=infinity; sudo systemctl daemon-reload;
     mkdir -p ~/.ssh; (grep -Pzo -q "Host \*\n  StrictHostKeyChecking no\n  IdentityFile ~/.ssh/sky-cluster-key\n  IdentityFile ~/.ssh/id_rsa" ~/.ssh/config) || printf "Host *\n  StrictHostKeyChecking no\n  IdentityFile ~/.ssh/sky-cluster-key\n  IdentityFile ~/.ssh/id_rsa\n" >> ~/.ssh/config;
diff --git a/sky/utils/kubernetes/deploy_remote_cluster.py b/sky/utils/kubernetes/deploy_remote_cluster.py
index 28d52b079..5fb80d1ff 100755
--- a/sky/utils/kubernetes/deploy_remote_cluster.py
+++ b/sky/utils/kubernetes/deploy_remote_cluster.py
@@ -723,14 +723,16 @@ def main():
                 # Do not support changing anything besides hosts for now
                 if history is not None:
                     for key in ['user', 'identity_file', 'password']:
-                        if history.get(key) != cluster_config.get(key):
+                        if not args.cleanup and history.get(
+                                key) != cluster_config.get(key):
                             raise ValueError(
                                 f'Cluster configuration has changed for field {key!r}. '
                                 f'Previous value: {history.get(key)}, '
                                 f'Current value: {cluster_config.get(key)}')
                     history_hosts_info = prepare_hosts_info(
                         cluster_name, history)
-                    if history_hosts_info[0] != hosts_info[0]:
+                    if not args.cleanup and history_hosts_info[0] != hosts_info[
+                            0]:
                         raise ValueError(
                             f'Cluster configuration has changed for master node. '
                             f'Previous value: {history_hosts_info[0]}, '
@@ -860,7 +862,7 @@ def deploy_cluster(head_node,
         use_ssh_config=head_use_ssh_config,
         # For SkySSHUpLineProcessor
         print_output=True)
-    if result is None:
+    if not cleanup and result is None:
         with ux_utils.print_exception_no_traceback():
             raise RuntimeError(
                 f'Failed to SSH to head node ({head_node}). '
diff --git a/sky/utils/resources_utils.py b/sky/utils/resources_utils.py
index 00a2582ee..5e4abbfa8 100644
--- a/sky/utils/resources_utils.py
+++ b/sky/utils/resources_utils.py
@@ -63,7 +63,9 @@ class NetworkTier(enum.Enum):
     def cli_help_message(cls) -> str:
         return (
             f'Network tier. Could be one of {", ".join(cls.supported_tiers())}'
-            f'. Default: {cls.STANDARD.value}')
+            f'. If {cls.BEST.value} is specified, use the best network tier '
+            'available on the specified instance. '
+            f'Default: {cls.STANDARD.value}')
 
     @classmethod
     def from_str(cls, tier: str) -> 'NetworkTier':


Test patch:
diff --git a/.github/workflows/smoke-tests-trigger.yaml b/.github/workflows/smoke-tests-trigger.yaml
index a69e422a2..4d75d9844 100644
--- a/.github/workflows/smoke-tests-trigger.yaml
+++ b/.github/workflows/smoke-tests-trigger.yaml
@@ -20,7 +20,7 @@ jobs:
         with:
           buildkite_api_access_token: ${{ secrets.BUILDKITE_TOKEN }}
           pipeline: "skypilot-1/smoke-tests"
-          branch: "master"
+          branch: ${{ github.ref_name }}
           commit: "HEAD"
           message: "Manual Smoke Tests: ${{ github.event.inputs.param }}"
           ignore_pipeline_branch_filter: true
diff --git a/tests/conftest.py b/tests/conftest.py
index fc36bd2bc..d6d0dfa93 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -200,6 +200,8 @@ def pytest_collection_modifyitems(config, items):
     for cloud in all_clouds_in_smoke_tests:
         skip_marks[cloud] = pytest.mark.skip(
             reason=f'tests for {cloud} is skipped, try setting --{cloud}')
+    skip_marks['postgres'] = pytest.mark.skip(
+        reason='skipped, because --postgres option is set')
 
     cloud_to_run = _get_cloud_to_run(config)
     generic_cloud = _generic_cloud(config)
@@ -232,6 +234,8 @@ def pytest_collection_modifyitems(config, items):
             item.add_marker(skip_marks['tpu'])
         if (not 'serve' in item.keywords) and config.getoption('--serve'):
             item.add_marker(skip_marks['serve'])
+        if ('no_postgres' in item.keywords) and config.getoption('--postgres'):
+            item.add_marker(skip_marks['postgres'])
 
     # Check if tests need to be run serially for Kubernetes and Lambda Cloud
     # We run Lambda Cloud tests serially because Lambda Cloud rate limits its
diff --git a/tests/smoke_tests/test_mount_and_storage.py b/tests/smoke_tests/test_mount_and_storage.py
index 0b72222aa..53d4b7376 100644
--- a/tests/smoke_tests/test_mount_and_storage.py
+++ b/tests/smoke_tests/test_mount_and_storage.py
@@ -1310,6 +1310,7 @@ class TestStorageWithCredentials:
 
     @pytest.mark.no_vast  # Requires AWS or S3
     @pytest.mark.no_fluidstack
+    @pytest.mark.no_postgres
     @pytest.mark.parametrize('store_type', [
         storage_lib.StoreType.S3, storage_lib.StoreType.GCS,
         pytest.param(storage_lib.StoreType.AZURE, marks=pytest.mark.azure),
@@ -1399,6 +1400,7 @@ class TestStorageWithCredentials:
 
     @pytest.mark.no_vast  # Requires AWS or S3
     @pytest.mark.no_fluidstack
+    @pytest.mark.no_postgres
     @pytest.mark.xdist_group('multiple_bucket_deletion')
     @pytest.mark.parametrize('store_type', [
         storage_lib.StoreType.S3, storage_lib.StoreType.GCS,
@@ -1443,6 +1445,7 @@ class TestStorageWithCredentials:
 
     @pytest.mark.no_vast  # Requires AWS or S3
     @pytest.mark.no_fluidstack
+    @pytest.mark.no_postgres
     @pytest.mark.parametrize('store_type', [
         storage_lib.StoreType.S3, storage_lib.StoreType.GCS,
         pytest.param(storage_lib.StoreType.AZURE, marks=pytest.mark.azure),
@@ -1471,6 +1474,7 @@ class TestStorageWithCredentials:
 
     @pytest.mark.no_vast  # Requires AWS or S3
     @pytest.mark.no_fluidstack
+    @pytest.mark.no_postgres
     @pytest.mark.parametrize('store_type', [
         storage_lib.StoreType.S3, storage_lib.StoreType.GCS,
         pytest.param(storage_lib.StoreType.AZURE, marks=pytest.mark.azure),
@@ -1713,6 +1717,7 @@ class TestStorageWithCredentials:
 
     @pytest.mark.no_vast  # Requires AWS or S3
     @pytest.mark.no_fluidstack
+    @pytest.mark.no_postgres
     def test_copy_mount_existing_storage(self,
                                          tmp_copy_mnt_existing_storage_obj):
         # Creates a bucket with no source in MOUNT mode (empty bucket), and
diff --git a/tests/unit_tests/test_nebius.py b/tests/unit_tests/test_nebius.py
new file mode 100644
index 000000000..465939e39
--- /dev/null
+++ b/tests/unit_tests/test_nebius.py
@@ -0,0 +1,116 @@
+from unittest.mock import MagicMock
+from unittest.mock import patch
+
+from sky import clouds
+from sky import resources as resources_lib
+from sky.clouds import nebius
+
+
+class TestNebiusNetworkTier:
+    """Test cases for Nebius network_tier functionality."""
+
+    def test_network_tier_unsupported_for_non_infiniband_gpus(self):
+        """Test that network_tier=best is unsupported for non-InfiniBand GPUs."""
+        # Test with L40S (should not support network_tier=best)
+        resources = resources_lib.Resources(cloud=nebius.Nebius(),
+                                            accelerators={'L40S': 4},
+                                            network_tier='best')
+
+        unsupported_features = nebius.Nebius._unsupported_features_for_resources(
+            resources)
+
+        # Should still have CUSTOM_NETWORK_TIER as unsupported
+        assert clouds.CloudImplementationFeatures.CUSTOM_NETWORK_TIER in unsupported_features
+
+    def test_network_tier_supported_for_h100_8gpu(self):
+        """Test that network_tier=best is supported for H100:8."""
+        resources = resources_lib.Resources(cloud=nebius.Nebius(),
+                                            accelerators={'H100': 8},
+                                            network_tier='best')
+
+        unsupported_features = nebius.Nebius._unsupported_features_for_resources(
+            resources)
+
+        # Should NOT have CUSTOM_NETWORK_TIER as unsupported
+        assert clouds.CloudImplementationFeatures.CUSTOM_NETWORK_TIER not in unsupported_features
+
+    def test_network_tier_supported_for_h200_8gpu(self):
+        """Test that network_tier=best is supported for H200:8."""
+        resources = resources_lib.Resources(cloud=nebius.Nebius(),
+                                            accelerators={'H200': 8},
+                                            network_tier='best')
+
+        unsupported_features = nebius.Nebius._unsupported_features_for_resources(
+            resources)
+
+        # Should NOT have CUSTOM_NETWORK_TIER as unsupported
+        assert clouds.CloudImplementationFeatures.CUSTOM_NETWORK_TIER not in unsupported_features
+
+    @patch('sky.skypilot_config.get_nested')
+    def test_no_infiniband_options_without_docker(self, mock_get_nested):
+        """Test that InfiniBand options are not added without Docker image."""
+        mock_get_nested.return_value = []  # No filesystems
+
+        # Create resources with H200:8, network_tier=best, but NO Docker image
+        resources = resources_lib.Resources(
+            cloud=nebius.Nebius(),
+            accelerators={'H200': 8},
+            network_tier='best',
+            instance_type='gpu-h200-sxm_8gpu-128vcpu-1600gb')
+        resources = resources.assert_launchable()
+
+        cloud = nebius.Nebius()
+        region = MagicMock()
+        region.name = 'us-central1'
+
+        deploy_vars = cloud.make_deploy_resources_variables(
+            resources=resources,
+            cluster_name='test-cluster',
+            region=region,
+            zones=None,
+            num_nodes=1)
+
+        # Check that Docker run options only include GPU access (if any)
+        docker_options = deploy_vars.get('docker_run_options', [])
+
+        # Should include GPU access
+        assert '--gpus all' in docker_options
+
+        # Should NOT include InfiniBand options since no Docker image
+        assert '--device=/dev/infiniband' not in docker_options
+        assert '--cap-add=IPC_LOCK' not in docker_options
+
+    @patch('sky.skypilot_config.get_nested')
+    def test_no_infiniband_options_without_network_tier_best(
+            self, mock_get_nested):
+        """Test that InfiniBand options are not added without network_tier=best."""
+        mock_get_nested.return_value = []  # No filesystems
+
+        # Create resources with H200:8, Docker image, but NO network_tier=best
+        resources = resources_lib.Resources(
+            cloud=nebius.Nebius(),
+            accelerators={'H200': 8},
+            image_id='docker:test-image:latest',
+            instance_type='gpu-h200-sxm_8gpu-128vcpu-1600gb')
+        resources = resources.assert_launchable()
+
+        cloud = nebius.Nebius()
+        region = MagicMock()
+        region.name = 'us-central1'
+
+        deploy_vars = cloud.make_deploy_resources_variables(
+            resources=resources,
+            cluster_name='test-cluster',
+            region=region,
+            zones=None,
+            num_nodes=1)
+
+        # Check that Docker run options only include GPU access
+        docker_options = deploy_vars['docker_run_options']
+
+        # Should include GPU access
+        assert '--gpus all' in docker_options
+
+        # Should NOT include InfiniBand options since network_tier != best
+        assert '--device=/dev/infiniband' not in docker_options
+        assert '--cap-add=IPC_LOCK' not in docker_options