> ## Documentation Index
> Fetch the complete documentation index at: https://namespace.so/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# SSH into a Container

> Fetch per-instance SSH credentials and open an interactive session inside a container.

Every instance can be reached over SSH without exposing a port. Namespace issues a per-instance key pair, so there is no ingress to configure and no password to manage.

`GetSSHConfig` returns everything needed to connect: a private key, the username to authenticate as, the host to dial, and the host keys to verify it against.

<Steps titleSize="h3">
  <Step title="Create an instance and wait for it">
    Unlike the command service, SSH needs the instance to be ready.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      const created = await computeClient.compute.createInstance({
        shape: { virtualCpu: 4, memoryMegabytes: 8192, machineArch: "amd64", os: "linux" },
        documentedPurpose: "ssh example",
        deadline: timestampFromDate(new Date(Date.now() + 60 * 60 * 1000)),
        containers: [
          { name: "shell", imageRef: "ubuntu:latest", args: ["sleep", "3600"] },
        ],
      });

      const instanceId = created.metadata!.instanceId;
      await computeClient.compute.waitInstanceSync({ instanceId });
      ```

      ```go Go theme={null}
      resp, err := computeClient.Compute.CreateInstance(ctx, &computepb.CreateInstanceRequest{
      	Shape: &computepb.InstanceShape{
      		VirtualCpu:      4,
      		MemoryMegabytes: 8 * 1024,
      		MachineArch:     "amd64",
      		Os:              "linux",
      	},
      	DocumentedPurpose: "ssh example",
      	Deadline:          timestamppb.New(time.Now().Add(1 * time.Hour)),
      	Containers: []*computepb.ContainerRequest{{
      		Name:       "shell",
      		ImageRef:   "ubuntu:latest",
      		Entrypoint: []string{"sleep", "3600"},
      	}},
      })
      if err != nil {
      	return err
      }

      md, err := computeClient.Compute.WaitInstanceSync(ctx, &computepb.WaitInstanceRequest{
      	InstanceId: resp.Metadata.InstanceId,
      })
      if err != nil {
      	return err
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Fetch the SSH credentials">
    `targetContainer` selects which container the session lands in, and can be omitted to land on the instance itself.

    The response carries `sshPrivateKey`, `username`, `endpoint`, and `sshHostKeys`. `endpoint` is a hostname such as `ssh.zrh2.namespace.so`, and `username` identifies what you are connecting to rather than being a fixed value.

    <Note>
      `sshHostKeys` is returned by the API but is not present on `GetSSHConfigResponse` in `@namespacelabs/sdk` 1.0.0, so the TypeScript SDK drops it. Read it over plain HTTP if you need to verify the host key.
    </Note>

    <CodeGroup>
      ```typescript TypeScript theme={null}
      const sshConfig = await computeClient.compute.getSSHConfig({
        instanceId,
        targetContainer: "shell",
      });

      // sshConfig.sshPrivateKey is a Uint8Array, sshConfig.username and
      // sshConfig.endpoint are strings.
      ```

      ```go Go theme={null}
      sshConfig, err := computeClient.Compute.GetSSHConfig(ctx, &computepb.GetSSHConfigRequest{
      	InstanceId:      resp.Metadata.InstanceId,
      	TargetContainer: "shell",
      })
      if err != nil {
      	return err
      }
      ```
    </CodeGroup>

    <Warning>
      The credentials are not part of the `CreateInstance` response. Fetch them with `GetSSHConfig`, or re-fetch the instance with `DescribeInstance` and read `extendedMetadata.sshMetadata`. A create response alone is not enough to connect.
    </Warning>
  </Step>

  <Step title="Open the session">
    There are two ways in, and they use different fields.

    The `endpoint` from `GetSSHConfig` is a regular SSH hostname, so any SSH client in any language can dial it with the returned key and username.

    Separately, instance metadata lists named services whose endpoints are websocket gateway URLs, for example `wss://gate.zrh2.nscluster.cloud/<instance-id>/22`. The Go SDK ships a helper that dials one of those and hands back a `net.Conn`, which `golang.org/x/crypto/ssh` then drives. That is the path `go/sidecar` takes.

    ```go Go theme={null}
    import (
    	"golang.org/x/crypto/ssh"
    	"namespacelabs.dev/integrations/nsc/ingress"
    )

    signer, err := ssh.ParsePrivateKey(sshConfig.SshPrivateKey)
    if err != nil {
    	return fmt.Errorf("failed to parse private key: %w", err)
    }

    config := &ssh.ClientConfig{
    	User:            sshConfig.Username,
    	Auth:            []ssh.AuthMethod{ssh.PublicKeys(signer)},
    	HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }

    conn, err := ingress.DialInstanceService(ctx, io.Discard, token, md.Metadata, "ssh")
    if err != nil {
    	return fmt.Errorf("failed to dial ssh: %w", err)
    }
    defer conn.Close()

    c, chans, reqs, err := ssh.NewClientConn(conn, "passthrough", config)
    if err != nil {
    	return fmt.Errorf("failed to create ssh connection: %w", err)
    }

    sshcli := ssh.NewClient(c, chans, reqs)
    defer sshcli.Close()
    ```

    From there a session behaves like any other SSH session, including PTY allocation and window resize for an interactive shell.

    <Info>
      The gateway helper is Go only. `DialInstanceService` has no TypeScript equivalent. Other languages should dial the `endpoint` from `GetSSHConfig` with an ordinary SSH library, or use [`nsc ssh`](/docs/reference/cli/ssh) instead of writing a client at all.
    </Info>
  </Step>
</Steps>

## Source

`go/sidecar` in [github.com/namespacelabs/examples](https://github.com/namespacelabs/examples) drives an interactive shell this way, including PTY handling.
That example assembles the credentials from `DescribeInstance` and builds the username itself as `"ctr-id:" + containerId`, which is the form for landing in a specific container. `GetSSHConfig` returns a `username` instead of requiring you to construct one.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.