Remote Work Tools

“It works on my machine” becomes “it works on your machine too” when dev environments are codified. Remote teams are especially exposed to environment drift — developers are on different OS versions, different tool versions, and different configurations. A template that runs consistently on day one means less async debugging and faster onboarding.


Option 1: Dev Containers (VS Code / JetBrains)

Dev Containers run your entire development environment inside a Docker container. VS Code and JetBrains connect to it ; the developer’s local machine is just a display layer.

Create .devcontainer/devcontainer.json in your repo:

{
  "name": "Project Dev Environment",
  "build": {
    "dockerfile": "Dockerfile",
    "context": ".."
  },
  "runArgs": [
    "--cap-add=SYS_PTRACE",
    "--security-opt", "seccomp=unconfined"
  ],
  "mounts": [
    "source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,readonly",
    "source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,readonly"
  ],
  "forwardPorts": [3000, 5432, 6379, 8080],
  "postCreateCommand": "make setup",
  "customizations": {
    "vscode": {
      "extensions": [
        "golang.go",
        "esbenp.prettier-vscode",
        "dbaeumer.vscode-eslint",
        "bradlc.vscode-tailwindcss",
        "ms-azuretools.vscode-docker"
      ],
      "settings": {
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "[go]": {
          "editor.defaultFormatter": "golang.go"
        }
      }
    }
  },
  "remoteUser": "vscode"
}

Create .devcontainer/Dockerfile:

FROM mcr.microsoft.com/devcontainers/base:ubuntu-22.04

# Install language runtimes
RUN apt-get update && apt-get install -y \
    curl wget git build-essential \
    postgresql-client redis-tools \
    && rm -rf /var/lib/apt/lists/*

# Go
ARG GO_VERSION=1.22.3
RUN curl -fsSL https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz \
    | tar -C /usr/local -xz
ENV PATH="/usr/local/go/bin:${PATH}"

# Node.js via nvm
ARG NODE_VERSION=20
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - \
    && apt-get install -y nodejs

# Tools
RUN go install github.com/air-verse/air@latest \
    && go install github.com/golang-migrate/migrate/v4/cmd/migrate@latest

# Set up non-root user
USER vscode
RUN curl -fsSL https://get.pnpm.io/install.sh | sh -

For teams using Docker Compose (multiple services):

{
  "name": "Full Stack Dev",
  "dockerComposeFile": [
    "../docker-compose.yml",
    "docker-compose.devcontainer.yml"
  ],
  "service": "app",
  "workspaceFolder": "/workspace",
  "postCreateCommand": "make setup"
}
# .devcontainer/docker-compose.devcontainer.yml
version: "3.8"
services:
  app:
    volumes:
      - ../..:/workspace:cached
    command: sleep infinity  # Keep container alive

Option 2: Nix Flakes (Reproducible Across All OS)

Nix flakes provide bit-for-bit reproducible environments. The same flake.nix produces identical tool versions on macOS, Linux, and in CI.

# Install Nix (macOS/Linux)
curl --proto '=https' --tlsv1.2 -sSf https://install.determinate.systems/nix | sh -s -- install

Create flake.nix in your repo root:

{
  description = "Project development environment";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs { inherit system; };
      in
      {
        devShells.default = pkgs.mkShell {
          buildInputs = with pkgs; [
            # Go toolchain
            go_1_22
            gopls
            golangci-lint
            air

            # Node.js
            nodejs_20
            nodePackages.pnpm

            # Database tools
            postgresql_16
            redis

            # Infrastructure
            terraform
            kubectl
            helm
            awscli2

            # Dev tools
            git
            gnumake
            jq
            yq
          ];

          shellHook = ''
            echo "Dev environment loaded"
            echo "Go: $(go version)"
            echo "Node: $(node --version)"

            # Set up local environment variables
            export GOPATH="$HOME/.local/share/go"
            export PATH="$GOPATH/bin:$PATH"

            # Load .env.local if it exists
            if [ -f .env.local ]; then
              set -a
              source .env.local
              set +a
            fi
          '';
        };
      }
    );
}

Enter the dev shell:

nix develop
# Or with direnv auto-activation:
echo "use flake" > .envrc
direnv allow

Option 3: Makefile Bootstrap (Universal)

For teams where Nix and Docker are too opinionated, a Makefile with a setup target provides a documented, repeatable setup that works anywhere:

# Makefile
.DEFAULT_GOAL := help
SHELL := /bin/bash

# Tool versions
GO_VERSION := 1.22.3
NODE_VERSION := 20
TERRAFORM_VERSION := 1.8.5

.PHONY: help
help: ## Show this help
	@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

.PHONY: setup
setup: check-deps install-tools setup-hooks setup-env ## Full dev environment setup
	@echo "Dev environment ready"

.PHONY: check-deps
check-deps: ## Check required system dependencies
	@command -v git  >/dev/null || (echo "ERROR: git not found" && exit 1)
	@command -v docker >/dev/null || (echo "ERROR: docker not found" && exit 1)
	@command -v make >/dev/null || (echo "ERROR: make not found" && exit 1)
	@echo "System dependencies OK"

.PHONY: install-tools
install-tools: ## Install project-specific tools
	@./scripts/install-tools.sh

.PHONY: setup-hooks
setup-hooks: ## Install git hooks
	@which pre-commit > /dev/null || pip install pre-commit
	pre-commit install
	pre-commit install --hook-type commit-msg
	@echo "Git hooks installed"

.PHONY: setup-env
setup-env: ## Set up local environment file
	@if [ ! -f .env.local ]; then \
		cp .env.example .env.local; \
		echo ".env.local created from .env.example — fill in your values"; \
	else \
		echo ".env.local already exists"; \
	fi

.PHONY: dev
dev: ## Start development services
	docker-compose -f docker-compose.dev.yml up -d
	air  # Or: npm run dev, etc.

.PHONY: test
test: ## Run tests
	go test ./...

.PHONY: clean
clean: ## Stop and clean development services
	docker-compose -f docker-compose.dev.yml down -v
#!/bin/bash
# scripts/install-tools.sh
set -euo pipefail

install_go() {
  if go version 2>/dev/null | grep -q "go${GO_VERSION:-1.22}"; then
    echo "Go already installed: $(go version)"
    return
  fi
  OS=$(uname -s | tr '[:upper:]' '[:lower:]')
  ARCH=$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/')
  curl -fsSL "https://go.dev/dl/go${GO_VERSION:-1.22.3}.${OS}-${ARCH}.tar.gz" \
    | sudo tar -C /usr/local -xz
  echo "Go ${GO_VERSION:-1.22.3} installed"
}

install_golangci_lint() {
  curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \
    | sh -s -- -b "$(go env GOPATH)/bin" latest
}

install_go
install_golangci_lint

Documenting the Template

Every repo should have an ONBOARDING.md that fits on one screen:

# Getting Started

## Prerequisites
- macOS 13+ / Ubuntu 22.04+ / Windows 11 + WSL2
- Docker Desktop 4.x
- VS Code with Dev Containers extension (recommended)
- Git 2.40+

## Setup (5 minutes)

```bash
git clone git@github.com:your-org/your-repo.git
cd your-repo

# Option A: Dev Container (recommended)
code . # VS Code prompts to reopen in container

# Option B: Local setup
make setup

Running the Project

make dev # Start all services
open http://localhost:3000

Environment Variables

Copy .env.example to .env.local and fill in values. Ask in #dev-setup for secrets. ```