Scoop as a local dev environment as code

Page content

In a tavern, a barkeeper scoops glowing developer tool icons out of a barrel into a patron’s new laptop, next to an unrolled script scroll and a forgotten pile of installers

Every few years I get a new machine, and every time I lose an afternoon to the same ritual: find the download page, pick the right installer, click Next a dozen times, fix the PATH, and discover two days later that I forgot Maven. I have been fighting this for a long time. Years ago I wrote about a portable Java 8 SDK on Windows and a more or less portable WebLogic 12c, which were really the same wish in disguise: I wanted my tools to be something I could carry, repeat, and throw away.

What finally got me there was not a portable folder of zip files. It was Scoop, a command-line installer for Windows. This post is about using it for something it was not exactly built for, which is describing a whole development environment as a file.

The environment is a file

Scoop installs programs from manifests kept in repositories called buckets. The main bucket has the command-line staples, extras has GUI applications, and java has a large collection of JDKs. Between them you find compilers, runtimes, build tools and editors: Git, Node.js, Python, Go, Maven, Gradle, gh, jq, kubectl, VS Code, DBeaver, PyCharm and a few thousand more.

And it does all of that without administrator rights, which I will come back to in a moment. So the whole machine setup can be a plain script like this one:

# setup-dev.ps1
if (-not (Get-Command scoop -ErrorAction SilentlyContinue)) {
    Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
    Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
}

scoop install git
scoop bucket add extras
scoop bucket add java

scoop install temurin17-jdk temurin21-jdk maven gradle
scoop install nodejs python go
scoop install gh jq kubectl
scoop install vscode dbeaver

scoop reset temurin21-jdk

That is the entire “setup guide”. It lives in a repository, goes through code review, and has a history. When a colleague needs Gradle, they open a pull request with one line. When someone asks why we have Go, git blame answers. Running the script twice is harmless: Scoop just says an app is already installed and moves on.

This is what I like most. The usual onboarding document is prose, and prose rots because nobody runs it. A script gets run, so it gets fixed.

No administrator required

This is the detail that makes the whole idea realistic, so I want to say it plainly: nothing in that script needs an administrator account. Scoop is installed from a regular, non-elevated PowerShell terminal, and everything it installs goes into ~\scoop, inside your own user profile. No UAC prompt, no installer wizard, no ticket to the IT department asking for elevated rights.

On a corporate laptop, where you are often a plain user without admin rights, that changes what is possible. The usual way of installing a compiler or an IDE, an installer that writes to Program Files and edits the machine-wide PATH, is exactly what is blocked there. With Scoop, the same tools install in seconds, and they only affect your user. It also makes the script safe to share: anyone on the team can run it on their own machine without asking permission first.

There are exceptions, and they are explicit. Installing an app globally with --global needs elevation, and a few apps in the nonportable bucket may trigger a UAC prompt. Everything I use for development, including the JDKs, the build tools and the editors, works as a plain user.

Day 200, not just day zero

Most ways of building an environment solve day zero and then abandon you. A fresh install is perfect on the first morning and out of date by the time the first release ships.

Scoop keeps going after the first day. scoop status tells me what is outdated, scoop update * brings everything to the latest version, and scoop cleanup * removes the old copies. And for the one tool that must not move, because the project is stuck on it, scoop hold freezes it while everything else keeps updating. The version of a tool stops being an accident of when I last visited its download page and becomes a decision I can see.

Many JDKs, one command

Java is where this pays off for me, because I never work with only one version. The java bucket carries Temurin, OpenJDK and several other distributions, with names like temurin8-jdk, temurin17-jdk and temurin21-jdk. They install side by side, each in its own folder.

Choosing which one is current takes one command:

scoop reset temurin17-jdk   # legacy project
scoop reset temurin21-jdk   # new project

No dialog to open, no JAVA_HOME to edit by hand, no stale entry hiding in the PATH. The same trick works for Python and other tools that come in several versions. For someone who keeps old and new systems alive at the same time, this alone justifies the whole approach.

Starting from the machine you already have

Not many of us start from a blank page. I have a laptop that already works, and I don’t want to retype what is on it. For that, Scoop can describe itself:

scoop export > scoopfile.json

The file lists the buckets and every installed app with its version and origin. On another machine, scoop import scoopfile.json adds the buckets and installs the apps. It is a quick way to capture what exists and then trim it into something worth keeping.

One detail is worth knowing, and I confirmed it by reading the import script of the version I have installed. The import installs whatever version the bucket currently offers. The version numbers in the file are only used for apps without a bucket manifest, and packages that were on hold are put back on hold. So the export is a list of what to install, not a lock file. If I need a specific version, I write it in my script as scoop install gh@2.7.0 or hold it after installing.

Your own bucket

A bucket is just a Git repository with JSON manifests, so a team can have its own. It is the place for the tools that no public catalog knows: the internal command-line client, the in-house code generator, the company’s pinned build of something. With a private bucket in the script, “the environment” also covers the things that were always the hardest to document.

One line to bootstrap

The best test of an environment-as-code is a brand new machine. With the script in a repository, the whole procedure is: open PowerShell, get the script, run it. Ten minutes later the machine is ready to build the project. Nothing in between asks me where I put the installers.

The same script is useful beyond people. A Windows build agent that is created fresh for each run can use the same file as a developer, which removes a whole category of “it works on the pipeline but not on my machine”. The same applies to an AI agent that needs a working toolchain: it can read the script to learn what is available.

The other patrons of the tavern

Scoop is not the only way to prepare a machine, and I don’t think it replaces them all. Here is how I see the neighbours.

The step-by-step guide. It is the most common solution and the weakest one. It is documentation that cannot be tested, and it is always a little wrong. A script is the same guide written in a language the machine understands.

Docker and dev containers. For services, databases and anything that runs on Linux in production, a container is the right answer, and I use them. But on Windows they cost more than they appear to: you need WSL 2 and virtualization, graphical IDEs sit awkwardly on the other side of the wall, and VPNs, proxies and corporate certificates tend to turn into a weekly annoyance. A container describes the runtime well. It does not remove the need for a JDK, an editor and a Git client on the host. Scoop handles that outer layer, and the two work well together.

Chocolatey and winget. These are the closest relatives. My difference with them is mostly the install model. Many packages want administrator rights, write to Program Files and touch the registry or the global PATH. Scoop keeps everything in the user’s folder and exposes tools through small shims, so it also works on locked-down company machines, and removing a tool leaves little behind. I haven’t done a rigorous comparison of the catalogs, and winget keeps improving, so check both for the tools you need.

Nix and similar tools. They solve reproducibility much more completely, but they ask for a steep learning curve and, on Windows, they live inside WSL. Scoop gives up some of that rigor in exchange for being understandable in an afternoon.

The folder of zip files. My old approach. It cannot update itself, it does not live in version control, and nobody else can reproduce it.

What Scoop is not

It would be dishonest to stop here. Scoop is Windows-only. It does not isolate anything: two tools that need conflicting libraries can still collide, because there is no container around them. By default it installs the latest version, so a strict reproduction needs explicit versions in the script, as I showed above. Manifests are kept by volunteers, so a new release can take a while to show up. And software that needs a system service, a driver or an elevated installer fits badly into the model, although the nonportable bucket exists for part of that.

So I would not claim that Scoop solves reproducible builds. It solves something more modest and more frequent: getting a developer’s machine from empty to useful, and keeping it that way.

A machine you can delete

The test I now apply to my own computer is whether I could wipe it without worry. If rebuilding it costs one command and ten minutes, the machine stops being a fragile thing I am afraid to touch and becomes something disposable. That freedom is what I was really looking for all those years ago with the portable Java folders. It just turned out that it needed a script instead of a zip file.