> ## 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.

# Running jobs in containers

export const KeepTabPosition = () => {
  useEffect(() => {
    const stateKey = "__namespaceKeepTabPosition";
    const releaseState = state => {
      state.instances -= 1;
      if (state.instances > 0) return;
      state.removeListeners();
      if (window[stateKey] === state) delete window[stateKey];
    };
    const existingState = window[stateKey];
    if (existingState) {
      existingState.instances += 1;
      return () => releaseState(existingState);
    }
    const maxSettleTime = 250;
    const requiredStableFrames = 3;
    const positionTolerance = 0.5;
    let animationFrame;
    let observer;
    let settleDeadline;
    let stableFrames = 0;
    let tabListToKeep;
    let tabListTop;
    const findTab = event => {
      if (!(event.target instanceof Element)) return null;
      return event.target.closest(".tab-container [role='tab']");
    };
    const findScrollContainer = element => {
      for (let parent = element.parentElement; parent; parent = parent.parentElement) {
        const {overflowY} = getComputedStyle(parent);
        if ((overflowY === "auto" || overflowY === "scroll") && parent.scrollHeight > parent.clientHeight) {
          return parent;
        }
      }
      return null;
    };
    const stopKeepingPosition = () => {
      if (animationFrame) cancelAnimationFrame(animationFrame);
      animationFrame = undefined;
      observer?.disconnect();
      tabListToKeep = undefined;
    };
    const restoreTabListPosition = () => {
      if (!tabListToKeep?.isConnected) return 0;
      const offset = tabListToKeep.getBoundingClientRect().top - tabListTop;
      if (Math.abs(offset) < positionTolerance) return offset;
      const scrollOptions = {
        top: offset,
        behavior: "instant"
      };
      const scrollContainer = findScrollContainer(tabListToKeep);
      if (scrollContainer) {
        scrollContainer.scrollBy(scrollOptions);
      } else {
        window.scrollBy(scrollOptions);
      }
      return offset;
    };
    const settlePosition = () => {
      const offset = restoreTabListPosition();
      stableFrames = Math.abs(offset) < positionTolerance ? stableFrames + 1 : 0;
      if (stableFrames >= requiredStableFrames || performance.now() >= settleDeadline) {
        stopKeepingPosition();
        return;
      }
      animationFrame = requestAnimationFrame(settlePosition);
    };
    observer = new MutationObserver(() => {
      stableFrames = 0;
      restoreTabListPosition();
    });
    const keepTabListInPlace = tab => {
      stopKeepingPosition();
      tabListToKeep = tab.closest("[role='tablist']");
      if (!tabListToKeep) return;
      tabListTop = tabListToKeep.getBoundingClientRect().top;
      settleDeadline = performance.now() + maxSettleTime;
      stableFrames = 0;
      observer.observe(document.getElementById("content") ?? document.documentElement, {
        attributes: true,
        attributeFilter: ["aria-selected", "class"],
        childList: true,
        subtree: true
      });
      animationFrame = requestAnimationFrame(settlePosition);
    };
    const selectWithoutNavigation = event => {
      if (!event.isTrusted) return;
      const tab = findTab(event);
      if (!tab) return;
      keepTabListInPlace(tab);
      event.preventDefault();
      event.stopPropagation();
      tab.click();
      restoreTabListPosition();
    };
    const selectWithKeyboard = event => {
      if (!event.isTrusted) return;
      const tab = findTab(event);
      const tabList = tab?.closest("[role='tablist']");
      if (!tab || !tabList) return;
      const tabs = Array.from(tabList.querySelectorAll(":scope > [role='tab']"));
      const currentIndex = tabs.indexOf(tab);
      let nextIndex;
      switch (event.key) {
        case "ArrowLeft":
          nextIndex = (currentIndex - 1 + tabs.length) % tabs.length;
          break;
        case "ArrowRight":
          nextIndex = (currentIndex + 1) % tabs.length;
          break;
        case "Home":
          nextIndex = 0;
          break;
        case "End":
          nextIndex = tabs.length - 1;
          break;
        case "Enter":
        case " ":
          nextIndex = currentIndex;
          break;
        default:
          return;
      }
      keepTabListInPlace(tab);
      event.preventDefault();
      event.stopPropagation();
      tabs[nextIndex]?.click();
      tabs[nextIndex]?.focus({
        preventScroll: true
      });
      restoreTabListPosition();
    };
    document.addEventListener("click", selectWithoutNavigation, true);
    document.addEventListener("keydown", selectWithKeyboard, true);
    const state = {
      instances: 1,
      removeListeners: () => {
        stopKeepingPosition();
        document.removeEventListener("click", selectWithoutNavigation, true);
        document.removeEventListener("keydown", selectWithKeyboard, true);
      }
    };
    window[stateKey] = state;
    return () => releaseState(state);
  }, []);
  return null;
};

export const CenteredImage = ({src, alt, width, caption, className}) => {
  const [basePath, setBasePath] = useState("");
  useEffect(() => {
    const path = window.location.pathname;
    setBasePath(path === "/docs" || path.startsWith("/docs/") ? "/docs" : "");
  }, []);
  return <Frame caption={caption} className={className} style={{
    maxWidth: width,
    marginInline: "auto"
  }}>
			<OptimizedImage src={`${basePath}${src}`} alt={alt} />
		</Frame>;
};

<KeepTabPosition />

Namespace Runners support using [custom containers for GitHub Jobs](https://docs.github.com/en/actions/using-jobs/running-jobs-in-a-container).
When a job runs inside a container, reaching Namespace resources, caches, git mirrors, and the local builder each need a small amount of extra mount and setup configuration.

## Accessing Namespace resources from containers

To access Namespace resources from within a container, extra configuration is required.
In particular, the directory `/var/run/nsc/` must be mounted into the container, and [`namespacelabs/nscloud-setup`](/docs/reference/github-actions/nscloud-setup) needs to be run.

See the following snippet for a working example of accessing Bazel:

```yaml {7,11} theme={null}
tests:
  runs-on: namespace-profile-my-profile-for-containers

  container:
    image: <my-image-ref>
    volumes:
      - /var/run/nsc/:/var/run/nsc/

    steps:
      - uses: actions/checkout@v4
      - uses: namespacelabs/nscloud-setup@v0
      - name: Setup Bazel cache
        run: |
          nsc bazel setup --remote=false --bazelrc /etc/bazel.bazelrc
      - name: Bazel test
        run: |
          bazel --bazelrc=/etc/bazel.bazelrc test //..
```

## Accessing cache volumes from containers

To access [Cache volumes](/docs/solutions/github-actions/caching#using-a-cache-volume) from within a container, additional configuration is required.

For example, when using an Ubuntu-based custom image, the following snippet provides a working, minimal example:

```yaml {7,9,10,15-17} theme={null}
tests:
  runs-on: namespace-profile-my-profile-for-containers

  container:
    image: <my-image-ref>
    env:
      NSC_CACHE_PATH: ${{ env.NSC_CACHE_PATH }} # env.NSC_CACHE_PATH contains the path to cache volume directory, that is `/cache`.
    volumes:
      - /cache:/cache # Where the cache volume is mounted.
    options: --cap-add=SYS_ADMIN # Required to by nscloud-cache-action to call `mount`.

  steps:
    - uses: actions/checkout@v4

    - name: Install sudo
      run: |
        apt-get update -y && apt-get install -y sudo

    - name: Setup cache
      uses: namespacelabs/nscloud-cache-action@v1
      with:
        cache: rust
```

Please see our [nscloud-cache-action documentation](/docs/reference/github-actions/nscloud-cache-action#advanced-running-github-jobs-in-containers) for details.

## Accessing Git mirrors from containers

If your workflow runs in a container, additional configuration is required to use [cached Git Repositories](/docs/solutions/github-actions/caching/git-checkouts):

```yaml {7,9} theme={null}
tests:
  runs-on: namespace-profile-my-profile-for-containers

  container:
    image: <my-image-ref>
    env:
      NSC_GIT_MIRROR: ${{ env.NSC_GIT_MIRROR }} # env.NSC_GIT_MIRROR contains the path to the git mirror directory.
    volumes:
      - /gitmirror:/gitmirror # Where the git mirror cache is mounted.

  steps:
    - name: Checkout with Namespace Git mirrors cache
      uses: namespacelabs/nscloud-checkout-action@v8
```

Please see our [nscloud-checkout-action documentation](/docs/reference/github-actions/nscloud-checkout-action#advanced-running-github-jobs-in-containers) for details.

## Using local build caching from containers

While Remote Builders provide the best performance for most scenarios, in-runner builders (locally cached) excel when building massive images (10GB+).
Keeping the build local skips network transfer time.

To access in-runner builders from within a container, additional configuration is required.

<Steps titleSize="h3">
  <Step id="enable-local-build-caching" title={<span>Enable local build caching</span>}>
    <Tabs>
      <Tab title="With runner profiles">
        To enable this feature, just open the [runner profile](https://cloud.namespace.so/workspace/actions/profiles) configuration and add a cache volume. Next, select `Locally cached` for your Docker builds.

        <CenteredImage width={500} alt="locally cached Docker build configuration" src="/docs/images/github-actions/in-runner-builder.webp" />
      </Tab>

      <Tab title="With runner labels">
        If using runner labels, first make sure you configure [a user cache](/docs/solutions/github-actions/caching#using-a-cache-volume) with labels and then add the following label `nscloud-in-runner-builder`.

        ```yaml {7} theme={null}
        jobs:
          tests:
            runs-on:
              - nscloud-ubuntu-22.04-amd64-8x32-with-cache
              - nscloud-cache-tag-example
              - nscloud-cache-size-100gb
              - nscloud-in-runner-builder

            steps:
              - name: Build and push
                uses: docker/build-push-action@v5
                with:
                  context: .
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step id="update-your-workflow" title={<span>Update your workflow</span>}>
    In the job step definition, you need to forward the buildkit socket and create a builder inside the container that uses the parent:

    ```yaml {6,10-12} theme={null}
    jobs:
      myjob:
        container:
          image: <my-image-ref>
          volumes:
            - /var/run/buildkit/buildkitd.sock:/var/run/buildkit/buildkitd.sock
      steps:
        - uses: actions/checkout@v4
        - ...
        - name: Configure builder
          run: |
            docker buildx create --driver remote --name parent --use unix:///var/run/buildkit/buildkitd.sock
        - ...
    ```
  </Step>
</Steps>

## Next steps

* [Runner Controls overview](/docs/solutions/github-actions/runner-controls)
* [Custom base images](/docs/solutions/github-actions/custom-base-images)
