ServiceNowDevelopment

Building a ServiceNow code-first workstation: Ubuntu 24.04, Docker, Coder and Node 24

Part 2 of 4 in the Code-first ServiceNow series. Code-first ServiceNow development needs a workspace you can rebuild in minutes and trust with credentials. Here is how I set one up: an Ubuntu 24.04 host, Docker, a Coder workspace template, Node 24, a pinned ServiceNow SDK and instance authentication that never touches Git.

AQ
Ali Qaiser
AWS Certified | ServiceNow Architect | Enterprise AI Consultant
26 September 2026
11 min read
Building a ServiceNow code-first workstation: Ubuntu 24.04, Docker, Coder and Node 24
In brief

Part 2 of 4 in the Code-first ServiceNow series. Code-first ServiceNow development needs a workspace you can rebuild in minutes and trust with credentials. Here is how I set one up: an Ubuntu 24.04 host, Docker, a Coder workspace template, Node 24, a pinned ServiceNow SDK and instance authentication that never touches Git.

Key Takeaways
  • Use a dedicated Ubuntu 24.04 LTS host. The repository recommends 4 vCPU, 8 GB RAM and 80 GB SSD for one developer, and 8 vCPU, 16 GB and 160 GB or more once AI agents and multiple workspaces run concurrently.
  • Coder turns the toolchain into a Terraform template, so a workspace can be thrown away and rebuilt without losing anything: source lives in Git, not in the container.
  • Standardise on Node.js 24 and enforce it three ways: `.nvmrc`, the `engines` field in `package.json`, and a bootstrap script that fails on the wrong major version.
  • Install the SDK per project from the committed lockfile with `npm ci`, and run it through `npx now-sdk`. Do not drift to whatever global version happens to be installed.
  • SDK credentials are stored by the SDK in the operating system's credential store, one alias per environment. No production alias belongs in an everyday developer workspace, and nothing sensitive ever goes into the repository.

Code-first ServiceNow, part 2 of 4. Part 1: Architecture · Part 2: Workstation setup (this article) · Part 3: SDK, Fluent and Git workflow · Part 4: Governance and AI agents

In part 1 I set out the principle behind this series: Git holds the application, and the ServiceNow instance is where it runs. That principle only works if every engineer, and every AI agent, builds from the same toolchain. If one person is on Node 20 with SDK 4.11 and another is on Node 24 with SDK 4.13, "reproducible" quickly becomes "works on my machine".

This part is the practical one. It covers the host, Docker, Coder, the workspace template, Node 24, installing the ServiceNow SDK and authenticating to instances without ever committing a credential. Everything here comes from my own practice repository and the lessons logged in its issues, checked against the current Coder, Node.js and ServiceNow SDK documentation.

Why a remote, reproducible workspace

The repository's architecture document states it simply: "Workspaces are disposable; source is persistent." A workspace should be rebuildable at any time, because the only things that matter live elsewhere: source in GitHub and a small amount of approved local state (npm cache, SDK authentication aliases) on persistent storage.

That matters for three reasons:

  • Onboarding becomes "create a workspace from the template" rather than a day of installing tools.
  • Drift is contained. The Node version, SDK version and editor extensions are defined once, in the template and the repository.
  • AI agents run inside a box you control, with the same tools and the same limits as a human, rather than on someone's laptop with whatever credentials that laptop happens to hold.

The target host

The design target is Ubuntu 24.04 LTS on a cloud VM with a static IP or stable DNS name. The repository's sizing guidance:

Use casevCPURAMDisk
Minimal lab24 GB50 GB
Recommended single developer48 GB80 GB
AI agents and multiple workspaces816 GB160 GB+

Coder's own Docker installation guide lists 2 CPU cores and 4 GB of free memory as the minimum. The larger sizes are project recommendations that leave headroom for SDK builds, agents and Docker workloads running side by side.

A candid note on status: the dedicated Ubuntu VM (issue #2) has not been provisioned yet. To keep moving, I stood up the first iteration on an interim local host: Docker Desktop on WSL2 on a Windows ARM laptop, running the Coder server and its PostgreSQL database as containers. That proved the workspace template and toolchain. The Ubuntu VM remains the target for a shared, always-on service with DNS and HTTPS, and the steps below are written for it.

Host bootstrap and hardening

Start with patches and a small set of base packages:

sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install -y \
  ca-certificates curl git jq unzip openssh-client gnupg lsb-release

The acceptance criteria in issue #2 double as a hardening checklist:

  • reachable only through an approved administrative path;
  • SSH key authentication, with password login disabled where practical;
  • only the required inbound ports exposed;
  • time synchronisation, a patch strategy and a backup approach defined;
  • the base package inventory documented, so the host can be rebuilt from instructions.

Docker Engine

Install Docker Engine and the Compose plugin from Docker's official repository for Ubuntu (follow Docker's current instructions rather than a copied script), then give your user access and verify:

sudo usermod -aG docker "$USER"
newgrp docker
docker version
docker compose version

Two security points deserve emphasis, both from the repository's security document:

  • Membership of the docker group is effectively privileged access to the host. Treat it like root.
  • Avoid mounting the host Docker socket into untrusted workspaces unless you understand the implications. A workspace with the socket can start privileged containers on the host. The Coder server container itself needs the socket to create Docker-based workspaces, which is precisely why workspace templates should not pass it through to the workspaces.

Never expose the Docker daemon over an unauthenticated TCP socket, and agree a log and disk clean-up policy (image and volume pruning) early. Build caches grow quickly on a busy host.

Coder: the control plane

Coder provides the workspaces. A single coder binary acts as both server and client, and the install script supports a stable release channel, which is what the repository standardises on for a durable installation:

curl -fsSL https://coder.com/install.sh | sh -s -- --stable
coder version

For a quick lab, coder server is enough. For a persistent shared service, the repository is explicit: run Coder behind TLS and use a durable PostgreSQL database, rather than treating the local process as disposable. Terminate TLS in a reverse proxy (Caddy, Nginx, Traefik or a cloud load balancer) on a real DNS name.

Network exposure should stay minimal:

PortPurposeExposure
22SSH administrationRestricted source IP or VPN
443Coder HTTPSApproved users
80Optional redirect or ACMEInternet only if required

Coder workspace agents normally connect outbound to the Coder server, so there is no need to open arbitrary workspace ports. For the control plane itself, the security document asks for HTTPS, SSO/OIDC where available, MFA at the identity provider, a small number of administrators, regular upgrades and restricted firewall rules.

Figure 2.1: Host topology. Engineers reach a reverse proxy over HTTPS and admins use restricted SSH; on the Ubuntu 24.04 VM the Coder server, backed by PostgreSQL, creates Docker workspace containers that push to GitHub and install only to ServiceNow PDI or DEVFigure 2.1: Host topology. Engineers reach a reverse proxy over HTTPS and admins use restricted SSH; on the Ubuntu 24.04 VM the Coder server, backed by PostgreSQL, creates Docker workspace containers that push to GitHub and install only to ServiceNow PDI or DEV

Backups

Back up the Coder PostgreSQL database, Coder configuration, the infrastructure-as-code templates and DNS and TLS configuration. Source code does not depend on VM backups, because GitHub remains authoritative. That is one of the quiet benefits of the model: losing the VM is an inconvenience, not a data loss event.

The ServiceNow workspace template

Coder templates are Terraform configurations. A template defines the workspace compute, the coder_agent that connects it back to the server, persistent storage, IDE applications, parameters and repository clone behaviour. The reference implementation starts with Docker workspaces on the same VM; a later version could move workspace compute to separate VMs, Kubernetes or a cloud provider without changing how developers work.

Every ServiceNow workspace built from the template should contain:

git, gh, curl, jq, openssh-client
Node.js 24 LTS and npm
TypeScript
ServiceNow SDK (project-local, from the lockfile)
VS Code / code-server
ServiceNow Fluent extension (servicenow.fluent-language-extension)
the project source

The Fluent extension recommendation is committed in the repository's .vscode/extensions.json, so VS Code prompts for it automatically.

The shape of the agent resource in a Docker-based template looks like this. It is an illustrative excerpt rather than the full template, which currently lives with the interim host's configuration:

resource "coder_agent" "main" {
  arch = data.coder_provisioner.me.arch
  os   = "linux"

  startup_script = <<-EOT
    set -e
    node --version   # expect v24.x
    if [ ! -d "$HOME/project" ]; then
      git clone https://github.com/<your-org>/<your-repo>.git "$HOME/project"
    fi
  EOT
}

The acceptance test for the template (issue #5) is deliberately simple: a new workspace can be created from zero and scripts/bootstrap-workspace.sh completes successfully.

What to persist, and what not to:

  • Persist: the workspace home directory where appropriate, the Git working tree, the SDK authentication store and the npm cache if useful.
  • Do not rely on: mutable container state for source or secrets.

Node 24 as the project standard

The repository standardises on Node.js 24 for two reasons: it is an LTS line, and it comfortably satisfies the ServiceNow SDK's prerequisite. The SDK's package metadata on npm declares node >=20.18.0, and ServiceNow's SDLC guide gives the same minimum.

The standard is enforced in three places:

  • .nvmrc contains 24, so local and remote environments agree on the major version;
  • package.json declares "engines": { "node": ">=24 <25", "npm": ">=11" };
  • scripts/bootstrap-workspace.sh exits if the Node major is not 24.

It is worth knowing the Node.js release calendar when you pick a standard. According to the Node.js release schedule, Node 24 ("Krypton") moves from active LTS to maintenance LTS on 20 October 2026 and is supported until 30 April 2028, while Node 26 is due to become LTS on 28 October 2026. Node 24 remains a sound choice for now; the move to 26 should be a deliberate, tested change like any other toolchain upgrade.

Two lessons from the interim build are worth passing on:

  • Version drift hides in images. At one point my laptop ran Node 24 while the first Coder workspace image used Node 20. Both "worked", which is exactly the problem. Aligning the image to the project standard closed the gap (issue #6).
  • Lockfiles can be platform-sensitive. A lockfile generated on Linux pulled a native build component that failed on Windows ARM ("not a valid Win32 application"). The fix was a clean reinstall on that platform. The broader lesson: generate and validate the lockfile on the platform your workspaces and CI actually use, and let a clean npm ci in CI tell you when it is out of step.

Installing the ServiceNow SDK: project-local and pinned

The SDK is a development dependency of the project, pinned exactly:

"devDependencies": {
  "@servicenow/sdk": "4.13.0",
  "@servicenow/glide": "27.0.5",
  "typescript": "5.5.4"
}

Install from the committed lockfile and run the SDK through npx, so every workspace uses the same version:

npm ci
npx now-sdk --version
npm run types
npm run build

For a brand-new application, the repository's workflow document shows the equivalent starting point:

mkdir my-servicenow-app && cd my-servicenow-app
npm init -y
npm install -D @servicenow/sdk@4.13.0 typescript
npx @servicenow/sdk init

The bootstrap script ties it together. It checks that git, curl, jq, node and npm exist, checks the Node major, prints versions, runs npm ci, shows the installed SDK version and configured authentication aliases, downloads dependency type definitions and runs a build:

Figure 2.2: Workspace bootstrap sequence. The script checks the tools and that Node's major version is 24, runs npm ci from the lockfile, confirms the SDK version, lists auth aliases without secrets, pulls type definitions and runs a buildFigure 2.2: Workspace bootstrap sequence. The script checks the tools and that Node's major version is 24, runs npm ci from the lockfile, confirms the SDK version, lists auth aliases without secrets, pulls type definitions and runs a build

Authenticating to instances without committing credentials

The SDK manages instance credentials itself. According to the SDK's CLI reference, credentials added with now-sdk auth are stored in the device keychain or credential manager, and passwords and long-lived refresh tokens are never printed by --list or --print. Each credential has an alias that other commands select with --auth.

# add a credential for a non-production instance (interactive prompts)
npx now-sdk auth --add https://<your-pdi>.service-now.com --type basic --alias pdi1

# see what is configured, and choose a default
npx now-sdk auth --list
npx now-sdk auth --use pdi1

--type accepts basic or oauth. For my first PDI I used basic authentication with an alias of pdi1, entered the credentials interactively, and nothing was written into the repository. For shared DEV and TEST instances, OAuth or another approved mechanism is the better choice. The SDK's getting-started guide also documents a non-interactive option for basic authentication, --password-stdin, which keeps the password out of process listings and shell history. And from 4.13.0, SDK commands using basic authentication can prompt for a six-digit TOTP code when the instance enforces MFA.

The rules from the repository's workflow and security documents:

  • one clear alias per environment;
  • no credentials in Git, ever;
  • no passwords in shell history where avoidable;
  • prefer OAuth or an approved secure mechanism;
  • production aliases should not exist in normal developer workspaces unless policy explicitly requires them.

Figure 2.3: Credential boundaries. Source, lockfile, config and keys live in Git with .env and .now ignored; SDK aliases for PDI and DEV stay in the workspace OS credential store and never reach the repo; per-environment secrets belong in a CI secret store (planned), with PROD only after human approval and no PROD alias in workspacesFigure 2.3: Credential boundaries. Source, lockfile, config and keys live in Git with .env and .now ignored; SDK aliases for PDI and DEV stay in the workspace OS credential store and never reach the repo; per-environment secrets belong in a CI secret store (planned), with PROD only after human approval and no PROD alias in workspaces

The repository's .gitignore already excludes .env, .sn_env, .now/, node_modules/, dist/ and log files. The security document lists what must never be committed: ServiceNow passwords, OAuth client secrets, access and refresh tokens, cookies, GitHub PATs, SSH private keys, .env files containing secrets, exported production or customer datasets and Coder admin tokens. If any credential appears in Git history, logs, screenshots, issue comments, chat transcripts or build artefacts, rotate it immediately. Deleting the commit is not enough.

A setup checklist

  • Ubuntu 24.04 LTS host sized for your workload, SSH keys only, minimal inbound ports.
  • Docker Engine and Compose from Docker's official repository; the Docker group treated as root; no socket pass-through to workspaces.
  • Coder on the stable channel, behind TLS, with persistent PostgreSQL and SSO where available.
  • A Terraform workspace template with Node 24, Git, GitHub CLI, code-server and the Fluent extension.
  • .nvmrc, engines and the bootstrap script all agreeing on Node 24.
  • The SDK installed per project with npm ci from a lockfile that matches package.json.
  • One SDK alias per non-production environment, stored by the SDK, never in Git; no production alias in workspaces.

With the workstation in place, part 3 moves on to the work itself: the project structure, Fluent code, builds, pull requests, CI gates, ATF and promotion.

The Code-first ServiceNow series

  1. Code-first ServiceNow: why Git, not the instance, now holds my application
  2. Building a ServiceNow code-first workstation: Ubuntu 24.04, Docker, Coder and Node 24 (you are here)
  3. From .now.ts to production: the ServiceNow SDK, Fluent and Git workflow
  4. Governing code-first ServiceNow: environment tiers, AI-agent guardrails and when to stay native

Sources

Expert Commentary

The workstation is where reproducibility is won or lost. Pin Node, pin the SDK through the lockfile, keep credentials in the OS store and out of Git, and make every workspace disposable. If rebuilding a workspace is scary, the setup is not finished.

Topics
ServiceNowServiceNow SDKCoderDockerNode.jsUbuntuDeveloper experience
All insights

Need Help With Your Implementation?

Get expert guidance from our certified ServiceNow and AWS architects.

Schedule a Consultation