← Back to Guide Index

Minimal Examples: Step-by-Step Templates

This guide provides copy-pasteable, step-by-step templates to go from a blank server to a working system. Follow these phases in order.

Phase 0: Server Setup

Before you begin, you need:

Step 1: Initial Server Setup

Connect to your server:

ssh root@your-server-ip
}}}

Update and install essentials:

apt update && apt upgrade -y

apt install -y vim git curl wget

}}}

Step 2: DNS Configuration

Add these DNS A records pointing to your server IP:

snek.cc.                A     YOUR_SERVER_IP
*.snek.cc.              A     YOUR_SERVER_IP
pds.snek.cc.            A     YOUR_SERVER_IP
*.pds.snek.cc.          A     YOUR_SERVER_IP
}}}

Wait for propagation:

dig +short snek.cc

dig +short test.pds.snek.cc

}}}

Step 3: Install NixOS

Download and boot the NixOS minimal ISO:

# From your server control panel
# 1. Mount the NixOS minimal ISO
# 2. Reboot into the ISO
}}}

Once booted into the NixOS installer:

# Partition your disk (example for single disk)

parted /dev/vda -- mklabel gpt

parted /dev/vda -- mkpart primary 512MiB -8GiB

parted /dev/vda -- mkpart primary linux-swap -8GiB 100%

parted /dev/vda -- mkpart ESP fat32 1MiB 512MiB

parted /dev/vda -- set 3 esp on

# Format partitions

mkfs.ext4 -L nixos /dev/vda1

mkswap -L swap /dev/vda2

mkfs.fat -F 32 -n boot /dev/vda3

# Mount partitions

mount /dev/disk/by-label/nixos /mnt

mkdir -p /mnt/boot

mount /dev/disk/by-label/boot /mnt/boot

swapon /dev/vda2

# Generate config

nixos-generate-config --root /mnt

# Edit configuration

vim /mnt/etc/nixos/configuration.nix

}}}

Basic configuration.nix:

{ config, pkgs, ... }: {
  imports = [ ./hardware-configuration.nix ];
  
  boot.loader.grub.enable = true;
  boot.loader.grub.device = "/dev/vda";
  
  networking.hostName = "snek";
  networking.useDHCP = true;
  
  time.timeZone = "UTC";
  i18n.defaultLocale = "en_US.UTF-8";
  
  users.users.atproto = {
    isNormalUser = true;
    home = "/home/atproto";
    extraGroups = [ "wheel" ];
    openssh.authorizedKeys.keys = [
      "ssh-ed25519 YOUR_PUBLIC_KEY"
    ];
  };
  
  services.openssh.enable = true;
  services.openssh.settings.PasswordAuthentication = false;
  
  environment.systemPackages = with pkgs; [
    vim git wget
  ];
  
  nix.settings.experimental-features = [ "nix-command" "flakes" ];
  system.stateVersion = "24.11";
}
}}}

Install:

nixos-install --no-root-passwd

reboot

}}}

Phase 1: Basic Services

Once NixOS is installed and you can SSH in as atproto:

Step 1: Set Up Flake

Create /etc/nixos/flake.nix:

{
  description = "snek.cc NixOS configuration";
  
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  };
  
  outputs = { self, nixpkgs }: {
    nixosConfigurations.snek = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [ ./configuration.nix ];
    };
  };
}
}}}

=== Step 2: Configure Caddy ===

Add to `configuration.nix`:

services.caddy = {

enable = true;

virtualHosts = {

"snek.cc" = {

extraConfig = ''

root * /var/www/snek.cc

file_server

header {

X-Frame-Options "SAMEORIGIN"

X-Content-Type-Options "nosniff"

}

'';

};

};

};

}}}

Create web root:

sudo mkdir -p /var/www/snek.cc
echo "

Hello from snek.cc

" | sudo tee /var/www/snek.cc/index.html }}} === Step 3: Test and Apply ===

cd /etc/nixos

sudo nixos-rebuild test --flake .#snek

}}}

If it works:

sudo nixos-rebuild switch --flake .#snek
}}}

Visit https://snek.cc - you should see your hello message!

== Phase 2: Add Bluesky PDS ==

=== Step 1: Generate PDS Keys ===

# Generate admin password

openssl rand -hex 16

# Generate JWT secret

openssl rand -hex 32

# Generate PLC rotation key

cd /tmp

openssl ecparam -name prime256v1 -genkey -noout -out plc-rotation.key

openssl ec -in plc-rotation.key -pubout -out plc-rotation.pub

# The private key hex is the PLC rotation key

}}}

Step 2: Set Up Secrets

Install sops-nix and configure secrets (see Secrets Setup for full details).

Briefly:

# Create .sops.yaml
mkdir -p /etc/nixos/secrets
}}}

Create `/etc/nixos/secrets/.sops.yaml`:

keys:

creation_rules:

key_groups:

}}}

Create /etc/nixos/secrets/pds.yaml with your secrets (encrypted).

Step 3: Configure PDS

Add to configuration.nix:

services.bluesky-pds = {
  enable = true;
  package = pkgs.bluesky-pds;
  
  environmentFiles = [ config.sops.templates."pds-env".path ];
  
  settings = {
    PDS_HOSTNAME = "pds.snek.cc";
    PDS_SERVICE_DID = "did:web:pds.snek.cc";
    PDS_PORT = 2583;
    PDS_DATA_DIRECTORY = "/var/lib/pds";
    PDS_BLOBSTORE_DISK_LOCATION = "/var/lib/pds/blocks";
    PDS_DID_PLC_URL = "https://plc.directory";
    PDS_BSKY_APP_VIEW_URL = "https://api.bsky.app";
    PDS_BSKY_APP_VIEW_DID = "did:web:api.bsky.app";
    PDS_INVITE_REQUIRED = "true";
  };
};

sops.defaultSopsFile = ./secrets/pds.yaml;
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];

sops.secrets.PDS_JWT_SECRET = { };
sops.secrets.PDS_ADMIN_PASSWORD = { };
sops.secrets.PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX = { };

sops.templates."pds-env".content = ''
  PDS_JWT_SECRET=''${config.sops.placeholder.PDS_JWT_SECRET}
  PDS_ADMIN_PASSWORD=''${config.sops.placeholder.PDS_ADMIN_PASSWORD}
  PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=''${config.sops.placeholder.PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX}
'';
}}}

=== Step 4: Add Caddy Config for PDS ===

Add to Caddy virtualHosts:

"*.pds.snek.cc, pds.snek.cc" = {

extraConfig = ''

tls {

on_demand

}

reverse_proxy http://127.0.0.1:2583 {

transport http {

read_timeout 0

write_timeout 0

dial_timeout 30s

}

}

'';

};

}}}

Step 5: Deploy

sudo nixos-rebuild switch --flake .#snek
}}}

=== Step 6: Create First User ===

export PDSADMINPASSWORD=$(sudo cat /run/secrets/PDSADMINPASSWORD)

pdsadmin create-account

}}}

Phase 3: Add Monitoring

Step 1: Configure Prometheus

services.prometheus = {
  enable = true;
  port = 9090;
  
  scrapeConfigs = [
    {
      job_name = "prometheus";
      static_configs = [{
        targets = [ "localhost:9090" ];
      }];
    }
  ];
  
  exporters.node = {
    enable = true;
    port = 9100;
  };
};
}}}

=== Step 2: Configure Grafana ===

services.grafana = {

enable = true;

settings = {

server = {

http_addr = "127.0.0.1";

http_port = 3001;

};

};

};

}}}

Step 3: Add Caddy Routes

"grafana.snek.cc" = {
  extraConfig = ''
    reverse_proxy http://127.0.0.1:3001
  '';
};
}}}

== Next Steps ==

Once you have these basics working:

1. Explore [[17-service-recipes|Service Recipes]] to add more services
2. Read [[03-caddy-reverse-proxy|Caddy Reverse Proxy]] to understand the proxy setup
3. Check [[14-troubleshooting|Troubleshooting]] if anything breaks
4. Review [[11-building-incrementally|Building Incrementally]] for development workflow

== Common Issues ==

*PDS not responding:*
- Check logs: `journalctl -u bluesky-pds -f`
- Verify DNS: `dig +short pds.snek.cc`
- Check secrets are decrypted: `ls -la /run/secrets/`

*Caddy not getting certificates:*
- Verify DNS points to server IP
- Check Caddy logs: `journalctl -u caddy -f`
- Temporarily disable firewall for testing

*Rebuild fails:*
- Check syntax: `nix-instantiate --eval ./configuration.nix`
- Review error messages carefully
- Test with `nixos-rebuild test` first