Documentation

Last updated: February 2026 · v0.1.0-alpha

Installation

Lochs can be installed with a single command on any Linux system with kernel 3.8 or later. The installer fetches the BSDulator engine, the FreeBSD 15.0-RELEASE base filesystem, and the Lochs CLI.

curl -fsSL https://lochs.dev/install.sh | sudo bash

Or install manually:

git clone https://github.com/lochs-dev/lochs.git
cd lochs
make
sudo make install
sudo lochs setup

System Requirements

RequirementMinimum
OSLinux (kernel 3.8+)
Archx86_64
PrivilegesRoot / sudo
Disk~2GB (FreeBSD base)
Packagesiproute2, iptables
CompatibleUbuntu, Debian, Fedora, Arch, RHEL, WSL2

Quickstart

After installation, create your first jail:

# Run a FreeBSD jail interactively
lochs run --name myjail --vnet -it freebsd:15 /bin/sh

# Or create and start separately
lochs create --name webserver --vnet --ip 10.0.0.10 -p 8080:80 freebsd:15
lochs start webserver
lochs exec webserver pkg install -y nginx

lochs run

Create and start a jail in one command. This is the most common way to launch a jail.

lochs run [OPTIONS] IMAGE [COMMAND]

Options:
  --name NAME        Assign a name to the jail
  --vnet             Enable virtual networking (VNET)
  --ip IP            Assign IPv4 address (requires --vnet)
  -p HOST:JAIL       Port forwarding (e.g. -p 8080:80)
  -it                Interactive terminal
  -d                 Detached mode (background)
  -e KEY=VALUE       Set environment variable
  -v HOST:JAIL       Bind mount volume
  --persist          Keep jail after process exits

lochs create

Create a jail without starting it. Useful for configuring before launch.

lochs create [OPTIONS] IMAGE
# Same options as `lochs run` except -it and -d

lochs build

Build a jail image from a Prisonfile (Dockerfile equivalent).

lochs build [OPTIONS] PATH

Options:
  -f FILE            Prisonfile path (default: ./Prisonfile)
  -t NAME:TAG        Name and tag the image
  --no-cache         Build without cache

lochs compose

Multi-jail orchestration using prison.yml configuration files.

lochs compose [OPTIONS] COMMAND

Commands:
  up         Create and start all jails
  down       Stop and remove all jails
  ps         List jails in the compose project
  logs       View combined output

Options:
  -f FILE    Compose file (default: ./prison.yml)
  -d         Detached mode

lochs exec

Execute a command inside a running jail.

lochs exec [OPTIONS] JAIL COMMAND

Options:
  -it        Interactive terminal
  -u USER    Run as user
  -e KEY=VAL Environment variable

lochs ps

List running jails with status, JID, IP, and ports.

lochs ps [OPTIONS]

Options:
  -a         Show all jails (including stopped)
  -q         Only display JIDs
  --format   Custom output format

lochs stop / rm

# Stop a running jail
lochs stop JAIL [--timeout SECONDS]

# Remove a jail (must be stopped)
lochs rm JAIL [--force]

# Stop and remove
lochs rm --force JAIL

lochs images

# List local images
lochs images

# Pull from registry
lochs pull freebsd:15

# Remove an image
lochs rmi IMAGE

lochs network

# List networks
lochs network ls

# Create a network
lochs network create --subnet 10.1.0.0/24 mynet

# Inspect a network
lochs network inspect mynet

# Remove a network
lochs network rm mynet

lochs logs

# View jail logs
lochs logs JAIL

# Follow log output
lochs logs -f JAIL

# Show timestamps
lochs logs -t JAIL

Prisonfile

The Prisonfile is a Dockerfile-equivalent for building jail images. It supports the following directives:

# Example Prisonfile
FROM freebsd:15
LABEL maintainer="dev@lochs.dev"

RUN pkg install -y nginx postgresql15-server

ENV PGDATA=/var/db/postgres/data15
ENV NGINX_CONF=/usr/local/etc/nginx

COPY ./nginx.conf $NGINX_CONF/nginx.conf
COPY ./init.sh /usr/local/bin/init.sh

RUN chmod +x /usr/local/bin/init.sh

EXPOSE 80 443 5432

WORKDIR /usr/local/www

CMD ["/usr/local/bin/init.sh"]
DirectiveDescription
FROMBase image (e.g. freebsd:15, freebsd:14)
RUNExecute command during build
COPYCopy files from host to image
ENVSet environment variable
EXPOSEDeclare network ports
WORKDIRSet working directory
CMDDefault command on start
ENTRYPOINTContainer entrypoint
LABELMetadata key-value pair
VOLUMEDeclare mount point
USERSet runtime user

prison.yml

Docker Compose-equivalent for multi-jail orchestration with dependency resolution.

# Example prison.yml
version: "1"
services:
  web:
    build: ./web
    ports:
      - "8080:80"
    depends_on:
      - db
    environment:
      - DATABASE_URL=postgresql://db:5432/myapp
    networks:
      - frontend
      - backend

  db:
    image: freebsd:15
    command: /usr/local/bin/pg_ctl start -D /var/db/postgres/data15
    volumes:
      - pgdata:/var/db/postgres/data15
    networks:
      - backend

networks:
  frontend:
    subnet: 10.0.1.0/24
  backend:
    subnet: 10.0.2.0/24

volumes:
  pgdata:

BSDulator Engine

BSDulator is the core syscall translation engine that powers Lochs. It uses ptrace() to intercept FreeBSD system calls and translate them to Linux equivalents at runtime, enabling unmodified FreeBSD binaries to execute on Linux hosts.

BSDulator is to FreeBSD what WINE is to Windows — a compatibility layer, not an emulator.

Architecture

FreeBSD Binary
    ↓ (executes FreeBSD syscall)
Ptrace Interceptor
    ↓ (catches syscall entry)
Syscall Translator
    ↓ (maps FreeBSD → Linux number + args)
ABI Translator
    ↓ (converts structures, flags, constants)
Linux Kernel
    ↓ (executes translated syscall)
Result Translator
    ↓ (converts return values back)
FreeBSD Binary (continues)

Syscall Translation Table

FreeBSD #Linux #NameStatus
30read
41write
52open
63close
5416ioctl
7377munmap
4779mmap
436jail_attach✓ emulated
506jail_get✓ emulated
507jail_set✓ emulated
508jail_remove✓ emulated
574__realpathatstub
596credsync✓ emulated

ELF Loader

BSDulator includes an ELF loader that handles FreeBSD binaries, including support for FreeBSD's dynamic linker (ld-elf.so.1). Both statically and dynamically linked executables are supported.

TLS Emulation

Thread Local Storage initialization is handled carefully — the thread pointer must be written at precisely the right time in the process lifecycle to avoid BSS section zeroing that would wipe critical threading data.

VNET Network Isolation

Each jail with --vnet enabled gets a dedicated Linux network namespace with a virtual ethernet pair connected to the host bridge.

Host System
┌────────────────────────────────────────┐
│  bsdjail0 (bridge) — 10.0.0.1/24      │
│     │                                  │
│     ├── veth0_j1 ←→ eth0 (jail1 ns)   │
│     │                 10.0.0.10/24     │
│     │                                  │
│     └── veth0_j2 ←→ eth0 (jail2 ns)   │
│                       10.0.0.20/24     │
└────────────────────────────────────────┘

Port Forwarding

Port forwarding uses socat to map host ports to jail ports:

lochs run --name web --vnet -p 8080:80 freebsd:15
# Host:8080 → Jail:80 via socat

Bridge Networking

The default bridge bsdjail0 is created automatically at 10.0.0.1/24. Custom networks can be created with lochs network create.

Jail Structure

Internal jail representation used by BSDulator:

typedef struct {
    int jid;                          // Jail ID
    int active;                       // Slot in use
    char name[256];                   // Jail name
    char path[1024];                  // Root path
    char hostname[256];               // Hostname
    struct in_addr ip4_addrs[16];     // IPv4 addresses
    int ip4_count;                    // Number of IPs
    int vnet;                         // Virtual network enabled
    int ns_net;                       // Network namespace FD
    int ns_mnt;                       // Mount namespace FD
    int ns_uts;                       // UTS namespace FD
    int attached_count;               // Attached processes
} bsd_jail_t;

Environment Variables

VariableDescriptionDefault
LOCHS_ROOTFreeBSD root filesystem path/usr/local/lochs/freebsd-root
LOCHS_DATAData directory for state/var/lib/lochs
LOCHS_BRIDGEDefault bridge interfacebsdjail0
LOCHS_SUBNETDefault jail subnet10.0.0.0/24
LOCHS_LOG_LEVELLogging verbosityinfo

Troubleshooting

Common Issues

"Permission denied" — Lochs requires root privileges. Run with sudo.

"Namespace not supported" — Your kernel may not have namespace support. Run lochs check to verify.

"Bridge already exists" — A previous session left networking state. Run lochs network prune.

TLS initialization failure — Known edge case with certain dynamically-linked binaries. Check BSDulator debug output with LOCHS_LOG_LEVEL=debug.