Introducing Spin (alpha)

February 15, 2025

Hello!

Hope everyone is having a good start to the year, I've been busy working on a number of projects and I'm here to talk about one I started last week, Spin.

Install Spin today!

  • Download the Release from GitHub Releases for the Repo

    • For MacOS you'll need to run the install_macos.sh inside the download, it will Symlink the current directory Spin binary into your path.

  • Check out the SpinCLI.dev website for documentation.

  • Let me know your bugs or thoughts on the GitHub Issues or Bluesky.

    • If you run into any bugs, post your spin.config.json and perhaps your Gemfile if the detection goes wrong.

What is Spin?

Spin is a development environment manager, the goal is to be able to have a consistent setup across systems or team members, and make it as easy as possible to get running with your applications.

The long-term goal is to enable people to run two commands spin fetch repo and spin up to get started with your app.

Features:

Spin has a number of features to help enable this long term vision including process management with Tmux, service management with docker (think for things like PostgreSQL and redis), and a neat dashboard showing all the running processes.

It was my first foray into writing Go and I'm pretty happy with how far it's come in a few days, much thanks to AI assistants for the help here. I won't pretend to have written this all myself, but the ideas and prompting were driven by me and my goals for the project.

How does it work?

Spin works by adding a JSON based config file at the root of your repository, spin.config.json. Let's look at a very real config file for a project of mine that I'm actively using Spin to develop with.

{
  "name": "relationkit.io",
  "version": "1.0.0",
  "type": "rails",
  "repository": {
    "organization": "afomera",
    "name": "relationkit.io"
  },
  "dependencies": {
    "services": [
      "postgresql",
      "redis"
    ],
    "tools": [
      "ruby",
      "bundler"
    ]
  },
  "scripts": {
    "server": {
      "command": "bundle exec rails server",
      "description": "Start Rails server",
      "hooks": {
        "pre": {
          "command": "bundle exec rails db:prepare",
          "description": "Prepare database"
        }
      }
    },
    "setup": {
      "command": "bundle install",
      "description": "Install dependencies",
      "hooks": {
        "post": {
          "command": "bundle exec rails db:setup",
          "description": "Set up database"
        }
      }
    },
    "test": {
      "command": "bundle exec rails test",
      "description": "Run Minitest tests",
      "hooks": {
        "pre": {
          "command": "bundle exec rails db:test:prepare",
          "description": "Prepare test database"
        }
      }
    }
  },
  "env": {
    "development": {}
  },
  "processes": {
    "procfile": "Procfile.dev"
  },
  "rails": {
    "ruby": {
      "version": "3.3.5"
    },
    "rails": {
      "version": "7.0.6"
    },
    "database": {
      "type": "postgresql",
      "settings": {
        "database": "relationkit_io_development",
        "host": "127.0.0.1",
        "port": "5432",
        "username": "postgres"
      }
    },
    "services": {
      "redis": true,
      "sidekiq": true,
      "action_cable": true
    },
    "assets": {
      "pipeline": "sprockets",
      "bundler": "esbuild"
    },
    "testing": {
      "framework": "minitest"
    }
  },
  "services": {
    "postgresql": {
      "type": "docker",
      "image": "postgres:17",
      "port": 5432,
      "environment": {
        "PGDATA": "/var/lib/postgresql/data/pgdata",
        "POSTGRES_HOST_AUTH_METHOD": "trust",
        "POSTGRES_PASSWORD": "postgres",
        "POSTGRES_USER": "postgres"
      },
      "volumes": {
        "data": "/var/lib/postgresql/data"
      },
      "health_check": {
        "command": [
          "pg_isready"
        ],
        "interval": "10s",
        "timeout": "5s",
        "retries": 3,
        "start_period": "40s"
      }
    },
    "redis": {
      "type": "docker",
      "image": "redis:7",
      "port": 6379,
      "volumes": {
        "data": "/data"
      },
      "health_check": {
        "command": [
          "redis-cli",
          "ping"
        ],
        "interval": "10s",
        "timeout": "5s",
        "retries": 3,
        "start_period": "30s"
      }
    }
  }
}

Yes it's a very long config file, but it's very prescriptive, and has a number of features like, services, and scripts sections.

Scripts

Scripts work similarly to NPM package scripts, except you can define a command and description, and hooks for them with pre and post hooks that run before and after the command specified. This is great for chaining commands and running scripts after one another. It runs bash commands too so you can always run a ruby or bash script to setup more commands if you need to!

Services

Services are how we specify what docker images and containers should be ran, you can manage them with the spin services command from the repo you're running. So spin services start redis spin services stop redis spin services logs redis and so forth. The commands in-terminal do have decent help documentation.

Right now a handful of services and containers are supported like PostgreSQL, Redis, MySQL and elastic search and a few others. I want to support more, even eventually allowing custom containers.

Let's look a few commands you'll need

spin ps

This command shows running processes for your apps.

spin services lists

This command lists all running services (containers, specified in spin.config.json).

CleanShot 2025-02-15 at 23.14.29@2x.png

Here's a small demo of how spin fetch works.

CleanShot 2025-02-15 at 23.17.08@2x.png

In this case, it's fetching a git repo afomera/api_with_versioning that's on my GitHub, it notices it doesn't have a spin.config.json file committed, so it generates one with the best guess we think it should have. Then you can cd into the folder and run spin up. Notice how it auto detects Procfile.dev, and will run commands off of that. You can specify a custom procfile in the config.json file, with the Processes key, and then procfile inside of it.

"processes": {
  "procfile": "Procfile_development"
}

Spin Dashboard

Spin Dashboard provides insight into usage of memory and cpu of your processes and provides a quick way to view logs for a process. If you need further logs or debugging for a process you can run spin logs process-name or spin debug process-name to get into the Tmux session. CTRL+D twice will get you out of the tmux session.

CleanShot 2025-02-15 at 23.21.56@2x.png

I'm super excited for Spin, and have been using it already for a few projects to work out bugs, though I know there's going to be more, depending on the app you're trying to use it for setup.

The Long Term Goal

I want Spin to be able to run Node and Ruby when needed and the system doesn't have it, but haven't worked out the best way to handle this. It's not meant to be a full version management tool like Mise or ASDF, those tools are great and I'll probably allow customization to use those tools too under the hood.

For now the long-term goal is on hold while we work out the project detection details, and process/service management bugs. I want these pieces to be solid before we add in bits about running Ruby or Node.

Long term you'll be able to get a fresh computer, download Spin, setup a few git configurations (like setting your SSH key, adding it to GitHub) and then running spin fetch repo, changing into the repo folder then running spin up to get running. All without node or ruby pre-installed.

Install Spin today!

  • Download the Release from GitHub Releases for the Repo

  • Check out the SpinCLI.dev website for documentation.

  • Let me know your bugs or thoughts on the GitHub Issues or Bluesky.

    • If you run into any bugs, post your spin.config.json and perhaps your Gemfile if the detection goes wrong.

Happy Spinning!

~ Andrea