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
| Requirement | Minimum |
|---|---|
| OS | Linux (kernel 3.8+) |
| Arch | x86_64 |
| Privileges | Root / sudo |
| Disk | ~2GB (FreeBSD base) |
| Packages | iproute2, iptables |
| Compatible | Ubuntu, 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"]
| Directive | Description |
|---|---|
FROM | Base image (e.g. freebsd:15, freebsd:14) |
RUN | Execute command during build |
COPY | Copy files from host to image |
ENV | Set environment variable |
EXPOSE | Declare network ports |
WORKDIR | Set working directory |
CMD | Default command on start |
ENTRYPOINT | Container entrypoint |
LABEL | Metadata key-value pair |
VOLUME | Declare mount point |
USER | Set 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 # | Name | Status |
|---|---|---|---|
| 3 | 0 | read | ✓ |
| 4 | 1 | write | ✓ |
| 5 | 2 | open | ✓ |
| 6 | 3 | close | ✓ |
| 54 | 16 | ioctl | ✓ |
| 73 | 77 | munmap | ✓ |
| 477 | 9 | mmap | ✓ |
| 436 | — | jail_attach | ✓ emulated |
| 506 | — | jail_get | ✓ emulated |
| 507 | — | jail_set | ✓ emulated |
| 508 | — | jail_remove | ✓ emulated |
| 574 | — | __realpathat | stub |
| 596 | — | credsync | ✓ 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
| Variable | Description | Default |
|---|---|---|
LOCHS_ROOT | FreeBSD root filesystem path | /usr/local/lochs/freebsd-root |
LOCHS_DATA | Data directory for state | /var/lib/lochs |
LOCHS_BRIDGE | Default bridge interface | bsdjail0 |
LOCHS_SUBNET | Default jail subnet | 10.0.0.0/24 |
LOCHS_LOG_LEVEL | Logging verbosity | info |
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.