
THERE IS NO CLOUD BUT YOUR OWN
A Technical D.I.Y Primer for Self-Hosting Readymade Logic in a World of Algorithms and Agents
On this page

A Technical D.I.Y Primer for Self-Hosting Readymade Logic in a World of Algorithms and Agents
While Networking Principles for DIY and Interactive Systems explores the infrastructure that our devices use to communicate, THERE IS NO CLOUD BUT YOUR OWN goes a step further and demonstrates that the open-source community has contributed a great deal of software that you can deploy today yourself.
This gesture now extends to intelligent systems by also deploying your own autonomous agents to collaborate with. We later experiment by encouraging these systems to lean into the absurd, unpredictable, and accidental.
By the end you have a Dadacenter and so does someone else and so does someone else and so does someone else and so does someone else...

Man Ray – The Gift (1921)

Lenovo - ThinkCentre Tiny Desktop Computer (2017)

Marcel Duchamp – Fountain (1917)
Futurism was an art movement that embraced acceleration, technology, speed, and disruption. Where Futurism celebrated the modern world, Dada was born out of disgust viewing the same logic, rationality, progress, and faith that Futurism admired as having collapsed into catastrophe. It emerged from the wreckage using irony, absurdity, and a sort of anti-art that redefined artistic value from a focus on technical craftsmanship to one centered on concept, meaning, and social commentary. Readymades are a concept introduced by Dada artist Marcel Duchamp, in which ordinary, mass-produced objects are presented as art with little or no alteration. The skill was no longer in the making but in the selecting, the placing, and the idea. A urinal becomes a fountain, an iron studded with tacks becomes a gift, and a mass-produced ThinkCentre pulled from corporate surplus becomes a substrate. Before mass-produced computation could sit on a shelf waiting to be repurposed, the computer itself had to be imagined, formalized, and built.
Alan Turing exposed weaknesses in secure communication systems as a cryptographer working to break the German Enigma cipher during World War II. This strengthened the design of modern cryptographic protocols that enable online banking, e-commerce, and private messaging. Beyond cryptography, his conceptual invention of the Turing machine represented the concept of a modern computer in an ideal form and used it to formally establish the capabilities and limitations of computation itself. The "Learning Machines" in his famous 1950 paper "Computing Machinery and Intelligence", Turing envisioned a radically different approach for Artificial Intelligence by suggesting that instead of attempting to program adult intelligence from the top down, it would be better to build "child-like" machines that arrive knowing nothing and are educated into capability through experience, reward, and a carefully chosen curriculum. This process has been extremly useful in solving problems that were too complex to program explicitly. The 2020s AI Revolution has made available a variety of Large Language Models (LLMs) that are "proprietary" (GPT-4, Gemini, Claude, Grok) and open-weight (Llama-3, Mistral, Gemma, DeepSeek).
Claude Shannon established the field of Information Theory with his seminal 1948 paper "A Mathematical Theory of Communication". In it, the bit is established as the fundamental unit of information and is used to measure the resolution of uncertainty in storing, sending, correcting, and compressing information across channels like telephone lines, fiber optic cables, and the radio spectrum. The decades that followed would revolutionize how the entire world communicates. Although Shannon is celebrated as the mathematician who gave the digital age its foundations, he can also be seen as a Dadaist in several interesting ways. After sparking the rise of the information age, he fucked right off and began entertaining his curiosity by creating all sorts of toys and gadgets that would have delighted Marcel Duchamp and baffled his colleagues at Bell Labs. He pursued absurdities like Juggling Machines and formalized the mathematical relationship between throw time, dwell time, number of balls and hands. A Motorized Pogo Stick and increasingly unhinged variations of the unicycle. But then there was the Flame-Throwing Trumpet, the Rocket-Powered Frisbee, and the Ultimate Machine which was a wooden box with a single switch that, when flipped, extended a mechanical hand, turned itself off, and retreated back inside.
John von Neumann accelerated the spread of the stored-program architecture in 1945 by publishing "First Draft of a report on the EDVAC¹" written while consulting for Eckert and Mauchly the inventors of ENIAC². ENIAC was commissioned by the U.S Army to calculate artillery firing tables. The successor, EDVAC enhanced ENIAC's design by introducing binary arithmetic and making the machine reprogrammable in software rather than physically altering the hardware circuits. The vast majority of computers share this architecture today whether it's a laptop, phone, server, microcontroller, or game console because they share the following components:
Electronic Discrete Variable Automatic Computer¹ Electronic Numerical Integrator and Computer²
Norbert Wiener was interested in modeling the similarities between how living organisms and machines regulate themselves by continuously sensing their current state, comparing it to a desired state, and using the difference to correct their own behavior. This observation became the transdisciplinary field known as Cybernetics through his 1948 publication "Cybernetics: Or Control and Communication in the Animal and the Machine". Cybernetics went on to influence not only computer science, robotics, and artificial intelligence but also psychology, anthropology, sociology, philosophy, and art. The name can be seen as a universal logic of self-correction and interconnectedness, and has an interesting lineage. It traces back to ancient Greek as κυβερνήτης (koo-bur-net-ees), a helmsman that keeps a ship on course by continuously reading drift and correcting for it. It was extended by Plato as metaphor for governance, and was repurposed again into the french vocabulary as cybernétique.
In the early days of the web, the Apache HTTP Server dominated as the most widely deployed HTTP server on the internet because it was open-source, freely available, and ran reliably across multiple operating systems. Rather than spending resources to engineer and develop similar proprietary web server software, developers could deploy Apache immediately. Over the course of the late 1990s and early 2000s, the number of websites expanded from only a few thousand to tens of millions, as internet adoption accelerated globally. This rapid growth enabled the creation of countless niche communities through discussion forums, personal blogs, and early e-commerce platforms.
The rise of Platforms like Facebook, Youtube, Instagram, Discord, and Reddit have somewhat centralized the internet by funneling vast amounts of user-generated content, discussion, and social interaction through a small number of corporate-controlled platforms, shaping trends, visibility, and the way people communicate online. Artificial intelligence is creeping into every facet of this digital ecosystem and beyond, from content recommendation algorithms and automated moderation to personalized advertisements and autonomous systems poking and nudging at your attention, preferences, and behavior. Ownership of hardware, software, and data is being increasingly funneled into subscription-based and cloud-controlled ecosystems, where users rely on proprietary platforms for access, storage, and services rather than owning or controlling their digital tools and content.
With the explosion of content and now AI-generated content, someday you may no longer be able to find that one song, video, website, or artist that you remember.
The Dadacenter is an assemblage of hardware you did not build, software you did not write, and AI you did not train. Provisioning your own anti-cloud is a far cry from slapping a sticker on a urinal, but the architectural pattern that once required an institution to deploy fits inside a shoebox on a shelf. The newest gesture in this assemblage is Agents driving Models to influence the Logic, and Operating System. Beneath them all, Hardware as a substrate. They observe their own state, correct their own failures, and pursue goals you set and steer in plain English.
The problem with a Readymade Stack is you don't know how anything works the higher up you go without peeling back each layer. When things aren't working as expected you don't know where to start troubleshooting. So this primer starts at the bottom and assembles a traditional private cloud that you can securely access wherever you are. Then we choose: stop here and have a homelab, or add Models and Agents and have a Dadacenter. Its purpose is to find out what happens when you run autonomous agentic systems on infrastructure you own to augment your environment as a practice.
Use whatever you have to get started, whether it's an old laptop or salvaged workstation. This is okay when learning and not yet committed because your found machine will likely consume more energy and therefore cost more over time. Newer Mini PCs with a modern energy-efficient processor are more rare and costly upfront, but will save you money in the long run. If the machine runs 24/7 then the difference between 15W and 100W depending on local electricity rate may roughly cost $50/year or $300/year. Equipping it with a GPU unlocks more capabilities like Real-Time Object Detection, Multiple High-Resolution Cameras, and Advanced Video Analytics, but automatically increases energy consumption. Specialized accelerators like the Coral Edge TPU share similar cost-saving benefits. Most of the effort is the initial time investment to learn and set everything up yourself. After that, maintenance becomes “very minimal”.
It's currently pretty expensive to upgrade your assembly to support fully independent Models and Agents. This will likely become more accessible in the near future as models improve, and more specialized hardware accelerators reach consumers. Those new accelerators are kind of like the next generation of gaming hardware. A few years from now you'll either own one the same way you own a console, or tap into them remotely the way Cloud Gaming works today.
Budgetmaxxing
Balancemaxxing
Siliconmaxxing
| Component | Minimum | Recommended | Notes |
|---|---|---|---|
| CPU | 4+ cores, x86_64 (Intel/AMD) | 8+ cores, x86_64 | No ARM; 6th gen Intel+ preferred |
| RAM | 8GB | 16GB | 32GB if multi-agent orchestration |
| Primary Storage | 256GB SSD | 512GB SSD | NVMe preferred for OS/Docker |
| Secondary Storage | 2TB HDD | 8TB HDD | Media storage; optional if CPU-only |
| Network | Fast Ethernet (100 Mbps) | Gigabit Ethernet (1 Gbps) | Multi-Gigabit Ethernet (2.5 Gbps) for |
| USB | 2x USB 3.0 | 2x USB 3.0 | Smart-home receivers, storage, etc. |
| PSU | 500W 80+ Bronze | 850W 80+ Gold | 1000W if RTX 3090 |
| Router | Consumer WiFi router with OpenWrt/Merlin | Tiny mini-PC running OPNsense | Optional; bridge WiFi AP to router |
There is no better example than the Linux Kernel and the many operating systems built around it. Forked into thousands of variants for every conceivable use. It is the non-physical canvas that all readymade logic uses to bootstrap an entire ecosystem of software and community-driven mutations. There are a few major flavors to choose from, but Debian will be our chosen operator. We will cover what commands and mechanisms are necessary to navigate, configure, and run the system. But running lots of apps directly on the operating system causes problems. Every application brings its own dependencies and assumptions about the machine it's running on, and eventually they contradict.
A key technology in simplifying self-hosting and making it more accessible are docker containers, which package applications and all their dependencies into portable, reproducible units that run the same way on any machine. Think of a container as a little computer that shares the host's kernel instead of booting its own, packing just the app and its dependencies into a sealed environment.
Building on this, entire platforms exist that assemble containers for you. If you are just starting out, platforms like CasaOS, UmbrelOS, and Cosmos accumulate these layers into a turnkey solution that orchestrates containers behind a dashboard that feels like an app store rather than a terminal. Beginners can start tinkering immediately and experienced hobbyists can quickly spin up entire Readymade Infrastructure specialized for families, small businesses, communities, and creative practices.
The main job of any operating system is abstracting the users and developers away from the nitty gritty. The Kernel acts as an intermediary between software applications and the computer’s hardware. It's the part you don't see but makes all the magic happen, and let's just leave it at that.
The shell is how you talk to your computer by typing instead of clicking. You type a command, hit enter, the computer does it. Bash (Bourne Again SHell) is the most common shell on Linux. It works the same way on a machine across the room or across the internet.
ssh username@hostname/ip - Secure Shell opens a shell on another machine
Tab — autocomplete; press twice for options
↑ / ↓ — scroll through command history
Ctrl-R — search command history
history — list recent commands
clear or Ctrl-L — clear the screen
pwd - shows current location in the filesystem
cd - move between locations in the filesystem
ls - list files
cp — copy; cp -r for directories
mv — move or rename
rm — delete; rm -rf deletes directories recursively
mkdir — make a directory; mkdir -p creates parent dirs as needed
touch — create an empty file or update its timestamp
cat — print a file to the terminal (short files)
grep — search inside files; grep -r "thing" . searches everything in the current directory
df -h — disk space, human-readable
nano — keyboard-driven text editor
vim or vi — keyboard-driven text editor
chmod — change permissions; chmod +x file makes a file executable
chown — change owner; chown user:group file
sudo — run a command as root (the one that makes things possible and breaks things irrevocably)
ps aux — list all running processes
top or htop — live process view (Task Manager)
kill — stop a process by PID
Ctrl-C — stop the current command
ping — is this host reachable?
curl — fetch a URL; curl -O saves it
wget — also fetches URLs, slightly different defaults
ss -tlnp — what's listening on which port
ip a — show network interfaces
systemctl status <service> — is it running?
systemctl start / stop / restart / enable / disable
journalctl -u <service> -f — live logs for a service
apt update — refresh the package list
apt upgrade — install available updates
apt install <name> — install something
apt remove <name> — uninstall
echo $VAR — print an environment variable
export VAR=value — set one for this session
which and whereis — find where a binary lives
~/.bashrc — the file you edit to make changes stick across sessions\
In the early internet the only option was to obtain a physical server, install an OS, and run your applications directly on bare-metal hardware. Most machines sat idle and were unable to safely share their resources across multiple apps without risking one crashing or compromising the others. The concept of carving one physical server into many virtual machines became known as hardware virtualization. This is done by a software layer known as a hypervisor that sits between the physical hardware and the guest operating systems, allowing each virtual machine to run as if it has the hardware to itself. This became the backbone of the cloud until developers realized they were shipping an entire operating system just to run a single application. Eventually, Docker became a lighter more convenient way to deploy apps and services to any environment by leveraging built-in Linux kernel features to isolate applications.
A Dockerfile answers "How do I create this environment?", and defines the base operating system, dependencies to install, files to copy, commands to run, and what boots first. Docker can then build a portable image or snapshot of that environment to store locally and push to a container registry for distribution and reuse. An instance of this image is known as a container, with its own filesystem, network interface, and isolated environment. Many open-source projects already maintain a Dockerfile and built image that is ready to use.
A Container Registry acts as a centralized library for readymade logic ready to deploy. They can be public or private and you can even run your own. They provide mechanisms for anyone to Pull/Push Images, Maintain versions, and Authenticate access.
The registries you'll actually encounter:
Every image reference is shorthand for a full address:
A fully qualified reference leaves nothing to assumption:
A single container can be started directly with docker run, which pulls the image if it's not already on disk, creates a container from it, and starts it in one step:
docker run -v $PWD:/data -it archivebox/archivebox initLists running containers:
docker psLists every container, including stopped ones:
docker ps -aStops a running container without deleting it:
docker stop <container name>Starts a stopped container back up:
docker start <container name>Deletes a stopped container permanently:
docker rm <container name>Opens a command inside a running container:
docker exec -it <container name> <command> <args0> <args1>Follows the live logs of a container:
docker logs -f <container name>Containers are short-lived by design. That is a feature, not a limitation. The same container image can be used to create many container instances across multiple computers, data centers, or geographic regions. In a Readymade App, the application logic can be separate from the data it operates on, and often is. Different apps may rely on data persistence while others may not. For example, if you use Immich to upload photos/video from your cell phone to your homelab, you want to be able to copy that data elsewhere for backups. The container can be replaced, but the uploaded media should live in a persistent volume or bind mount so it survives.
Each new container will maintain a temporary storage layer. That layer survives docker stop and docker start — the container still exists, it's just not running. The moment you run docker rm, the container and its writable layer are gone permanently. The image it came from is untouched and can spin up a new container at any time. Anything that needs to outlive the container goes in a volume or bind mount
Run without --rm — container persists after exit, writable layer intact:
docker run --name mycontainer nginxStop it — container still exists, writable layer intact:
docker stop mycontainer
docker start mycontainer # comes back, data still thereRemove it — writable layer gone:
docker rm mycontainerRun with --rm — container removes itself on exit, writable layer gone:
docker run --rm --name mycontainer nginxDocker can create separate storage volumes that exist outside the lifecycle of a container. They can be automatically or intentionally created and will remain until explicitly deleted with docker volume rm or docker volume prune regardless of how many times the container using them is created, stopped, or destroyed. Volumes are preferred for sharing data between containers. Multiple containers can mount the same volume at the same time. The contents of the volume exist on disk at /var/lib/docker/volumes/ but are tucked away and managed by docker. Use bind mounts instead if you need direct access to the files from the host. Some Readymade Apps prefer using a Docker Volume and will automatically create one when they are started for the first time.
Let Docker create the volume automatically at run time:
docker run -v mydata:/app/data nginx Create a volume explicitly then use it:
docker volume create mydata
docker run -v mydata:/app/data nginxVolumes can be mounted as Read-Only:
docker run -v mydata:/app/data:ro nginxvolumes:
- mydata:/app/data:roInspecting and managing volumes:
docker volume ls # list all volumes
docker volume inspect mydata # see where it lives on disk
docker volume rm mydata # delete a specific volumeremoves any volume that is not currently mounted to at least one container — running or stopped:
docker volume prune # delete all unused volumesBind mounts are preferred when you need to share data between a container and the host. Most Readymade Apps ship with a config file that controls their behavior like Home Assistant has configuration.yaml, and Frigate has config.yml. Some apps need an entire directory bind mounted rather than one config file because they write multiple files into it, or because the directory is the workspace the app operates on. Immich requires an data/ directory for incoming photos/videos. Frigate requires a media/ directory for recordings and clips.
Bind Mount Syntax [host path] : [container path]:
-v /host/path:/container/pathMounting a host directory into a container:
docker run -v /srv/appdata:/app/data nginxMounting a single configuration file:
docker run -v /srv/homeassistant/configuration.yaml:/config/configuration.yaml home-assistantThe Default Bridge is the network Docker automatically creates. Every container ran without specifying a --network will attach itself to this network and get assigned an ip address from default pool 172.17.0.0/16. It's considered legacy and in practice user-defined networks are created automatically which allow you to use container names as a substitute for the ip addresses. Container names are stable while IPs are not. Containers on the default bridge inherit the host's DNS settings and can only reach each other by IP. Containers on a user-defined network get Docker's embedded DNS server (127.0.0.11), which resolves container names automatically if they are on the same network. A container on Network A cannot resolve a container on Network B by name. A container attached to multiple networks can resolve names across all of them.
When you create a user-defined network whether automatically through docker-compose.yaml or docker network create that network and it's containers are assigned ip addresses from an internal predefined "default address pools" that can be overwritten in /etc/docker/daemon.json
By default Docker gives you room for 31 networks total. Every docker compose up consumes one. On a dense homelab you will hit the ceiling before you realize it. No warning, just a failed docker network create when you try to stand up your 31st "Readymade App".
The built-in default will use:
{
"default-address-pools": [
{ "base": "172.17.0.0/16", "size": 16 }, // 1 Network/65,534 hosts each
{ "base": "172.18.0.0/16", "size": 16 }, // 1 Network/65,534 hosts each
{ "base": "172.19.0.0/16", "size": 16 }, // 1 Network/65,534 hosts each
{ "base": "172.20.0.0/14", "size": 16 }, // 4 Networks/65,534 hosts each
{ "base": "172.24.0.0/14", "size": 16 }, // 4 networks/65,534 hosts each
{ "base": "172.28.0.0/14", "size": 16 }, // 4 networks/65,534 hosts each
{ "base": "192.168.0.0/16", "size": 20 } // 16 networks/4,094 hosts each
]
}Every network is burning thousands of host addresses while most apps/services only really need 3-6.
In a long-term homelab, you might eventually exceed Docker’s default pool of roughly 31 bridge networks. But you are very unlikely to need more than 254 user-defined networks, and even less likely to need 254 container IPs inside each one.
Check your current daemon.json first:
cat /etc/docker/daemon.jsonIf the file is empty or doesn't exist, write the config directly:
echo '{"default-address-pools":[{"base":"10.200.0.0/16","size":24}]}' | sudo tee /etc/docker/daemon.jsonThen restart Docker:
sudo systemctl restart dockerBy default a container's ports are only reachable from the Docker host and other containers on the same network (bridge, user-defined). No other device on your LAN or across the internet can reach it until you expose it. Two ways to expose a container to the outside world are by publishing a port or in some cases having the container share the host's network.
-p 8080:80 is shorthand for --publish and specifies the port number on your host machine where you want to receive traffic, and the port number within the container that's listening for connections.
using docker run with --network host or docker compose with network_mode: host will make the container share the host's network. This means if any processes in the container are listening on any ports then those processes are available at the Host IP Address.
The docker-compose.yaml for Home Assistant uses network_mode: host to discover devices via mDNS and broadcast protocols that aren't made to announce presence across separate networks.
More fully-featured self-hosted projects will typically require communication across multiple containers such that one container may run the application server (e.g. a web app written in Node.js, Django, or Flask), another container may run a database (e.g. PostgreSQL, MySQL), and a third container may run a cache or message broker (e.g., Redis, RabbitMQ). A docker-compose.yaml file makes it easy to modify the configuration of each container and simplifies the processes of defining and running multi-container Docker apps.
I think it's easier to stay organized by explicitly converting a docker run command to a docker-compose.yaml file instead. Not every projects will provide a docker-compose.yaml and .env.example but will give a docker run command only
A YAML is a type of text file structured in a certain way that serves as a clean, human-readable format to define settings, automations, and infrastructure on your computer, on your server, or on your cloud.
Docker Compose commands read and operate on the docker-compose.yaml (deprecated) or compose.yaml found in the current working directory.
/opt/
├── immich/
│ ├── compose.yaml ← docker compose up -d (runs Immich stack)
│ ├── .env
│ └── library/
│
├── frigate/
│ ├── docker-compose.yaml ← docker compose up -d (runs Frigate stack)
│ ├── .env
│ └── config/
│
└── wg-easy/
├── compose.yaml ← docker compose up -d (runs wg-easy stack)
├── .env
└── config/
Following logs on a single service:
docker compose logs -f service_nameStatus overview scoped to just that project's containers:
docker compose psThe Standard update cycle:
docker compose pull
docker compose up -dBuilds/pulls images if needed and starts every service in the stack as a background process:
docker compose up -dStops and removes the containers and networks from up, leaving volumes and data intact.
docker compose downStops the running containers without removing them, leaving them in hiatus:
docker compose stopResumes the stopped containers:
docker compose startWhen you run docker compose up, Compose automatically reads a .env file in the same directory as the docker-compose.yaml and substitutes any variables it finds into the docker-compose.yaml. This allows you to modify secrets and environment-specific values without hardcoding them.
Example .env for Immich
UPLOAD_LOCATION=./library
DB_DATA_LOCATION=./postgres
IMMICH_VERSION=release
DB_PASSWORD=mysecret
DB_USERNAME=postgressimplified docker-compose.yaml for Immich
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
volumes:
- /srv/media-storage/immich:/data
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ports:
- '2283:2283'
depends_on:
- database
restart: always
healthcheck:
disable: false
database:
container_name: immich_postgres
image: ghcr.io/immich-app/postgres
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
POSTGRES_INITDB_ARGS: '--data-checksums'
volumes:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
shm_size: 128mb
restart: always
The Apache HTTP Server was a ready-made, free software platform that allowed anyone with an internet connection to host and serve content to users across the world. After decades of open-source development, we now have a vast ecosystem of software or "readymade logic" that goes above and beyond from just serving web pages to complete production-ready systems that replicate major cloud platforms akin to Google Suite (Docs, Photos, Drive, Sheets, etc), Dropbox, ChatGPT, Notion, Discord, and much more. Visit https://awesome-selfhosted.net/ for an idea of what's possible.
A few ready-made free software platforms you can find are:
Examples: Immich, Nextcloud, Trilium
Immich is a self-hosted photo/video cloud platform that allows family and friends to connect and use your infrastructure as cloud storage. It has features like Facial recognition, person clustering, memories, semantic search, mobile backup, and family-friendly multi-user sharing. All this and more, but how do you install this and get it running?
Like most Readymade Logic available, the Docker Compose is commonly provided along with specific instructions.
docker compose up -d, allowing Docker to download the required images and create the application stack.Examples: mosquitto-broker, uptime-kuma, AdGuard Home
Smaller projects may only require a single Docker image and can be started with a single command.
docker run -it -p 1883:1883 -v "$PWD/mosquitto/config:/mosquitto/config" eclipse-mosquitto
Examples: Tailscale, CasaOS, UmbrelOS
Very commonly given are one-liner bash scripts that perform heavy lifting:
A Native Install, the script installs the application directly onto the OS.
curl -fsSL https://tailscale.com/install.sh | shA Docker Bootstrap, the script installs Docker if missing, then deploys the actual application as a container or compose stack on your behalf.
curl -fsSL https://get.casaos.io | sudo bashExamples: WireGuard, openssh-server
Some software is installed directly on the operating system using the package manager.
sudo apt install wireguard -y
As your Dadacenter grows, a few supporting services become increasingly useful for making your applications easier to find, access, and secure both inside and outside your local network. These are first introduced but partly demonstrated in the One Hop Beyond (Networking Detour) section as needed.
Examples: AdGuard Home, Pi-hole
DNS (Domain Name System) translates human-friendly names into IP addresses. DNS rewrites allow you to create your own names for devices and services on the local network. For example, immich.home.arpa can automatically resolve to 192.168.50.10, making services easier to find and remember. AdGuard Home and Pi-hole act as DNS forwarders. They answer local records such as immich.home.arpa, block unwanted domains, and forward everything else to an upstream DNS provider such as Cloudflare, Google, Quad9, or your ISP.
By default, most routers provide DNS services to devices on the network and forward requests to an ISP or public DNS provider.
A common Dadacenter setup is running AdGuard Home or Pi-hole on a dedicated machine and configuring the router to advertise it as the network DNS server.
Some routers, firewalls, and network appliances can run AdGuard Home, Pi-hole, or similar DNS software directly on the network edge.
Examples: WireGuard, Tailscale
A Virtual Private Network (VPN) securely extends your private network across the internet. It allows you to access your Dadacenter from a phone, laptop, or tablet even when you are away from home.
A VPN server runs inside your Dadacenter and devices connect directly to it over the internet.
A third-party service coordinates device discovery and connectivity for you.
Examples: Nginx Proxy Manager (NPM), Traefik, Caddy
A reverse proxy acts as a front door for your web services. Instead of remembering ports like :2283, :8123, or :5000, it routes incoming requests to the correct application like immich.home.arpa, frigate.home.arpa, and so on. It's also the go to method for automatically managing HTTPS certificates and safely exposing multiple web services through a single connection to your router.
Applications are accessed directly through their IP address and port number.
A reverse proxy receives incoming web traffic and routes requests to the correct service.
Modern reverse proxies can automatically obtain and renew TLS certificates, allowing applications to be securely accessed over HTTPS.
Examples: Cloudflare DDNS, Router-Based DDNS, DDClient, DuckDNS
Most residential internet connections receive a public IP address that can change over time. Dynamic DNS updates your DNS records automatically so your domain name continues pointing at your Dadacenter even when that address changes.
Some internet connections provide a permanent public IP address that never changes.
A service periodically updates DNS records whenever your public IP address changes.
Many modern routers and firewalls can automatically update DNS records without requiring additional software.
Examples: Cloudflare Tunnel, Tailscale Funnel, frp
The Dadacenter may be operating behind restricted ISP's firewalls that block incoming traffic. You may not have access to the router to forward ports. Your ISP may not have assigned you a real public IP at all, instead placing you behind Carrier-Grade NAT (CGNAT) sharing one address across hundreds of households. A reverse tunnel takes advantage of nearly every firewall's leniency for clients within the network communicating outside to websites and services listening on port 443/tcp. After setting one up, traffic is relayed through a middleman server to the app or service running on the Dadacenter, without ever requiring an open port.
Cloudflare Tunnel, ngrok, and Tailscale Funnel handle the relay infrastructure for you. Zero server management, works immediately, but your traffic passes through their infrastructure.
frp running on a cheap VPS you control. Same mechanics as third party but Traffic routes through your infrastructure.
Without further knowledge and configuration, your Dada Center is only accessible when you are within reach of its local network. While Networking Principles for DIY and Interactive Systems provides a practical anchor for these concepts, its coverage is brief. To extend this tether across mobile data carriers, external Wi-Fi, or public hotspots - you must bridge the gap between your private internal network across the public internet to your device. This is usually done through one of three practical methods depending on several conditions like:
These are not mutually exclusive and a Reverse Proxy, VPN, and Reverse Cloud Tunnel can all coexist serving different purposes.
Use a Reverse Proxy (e.g Nginx) when you have multiple services that need to be accessible over the internet to anyone and everyone¹.
Use a VPN (e.g. WireGuard, Tailscale) when services only need to be accessible to you or a trusted group.
Use a Reverse Cloud Tunnel (e.g Cloudflare Tunnel) when your server has no true public IP Address (CGNAT) but it needs to be accessible over the internet to anyone, and/or needs to obfuscate its Public IP Address.
¹Exposing your services to the public internet to anyone and everyone means that web crawling bots will almost immediately detect your server and begin probing it by scanning for open ports, known vulnerabilities, default credentials, and outdated software versions. This happens automatically and constantly, it's not targeted or personal, the internet is just being swept by bots 24/7. So it's best practice to learn about and enforce several layers of security.
WireGuard VPN solves: How do I reach my stuff?
Reverse Proxy solves: How does the Internet reach my stuff directly?
Reverse Cloud Tunnel solves: How does the Internet reach my stuff indirectly through a third party?
Tailscale sidesteps all of this, which is why it has become popular. But convenience has a cost and Tailscale's control plane is a third party. The goal here is independent infrastructure you own, understand, and control. Tailscale is a useful tool, but it's not the destination unless you also deploy the control plane yourself.
How are you supposed to remember that http://192.168.50.10:2283 is running Immich, http://192.168.50.10:8971 is running frigate, http://192.168.50.10:8123 is running home-assistant, and so on? It's okay to use unsecure http in your own LAN, but without TLS your login credentials, session tokens, photos, and everything else travel in plaintext. Anyone on the same network can see it. Also Modern browsers require HTTPS for anything beyond basic page rendering, and your self-hosted apps increasingly depend on those browser features.
You're running 15 services and nobody can remember which port is which. AdGuard Home or Pi-hole let you create DNS rewrites so when you are connected to your network immich.home.arpa resolves to 192.168.50.10. Now you type a name instead of a number (http://immich.home.arpa:2283). You traded the IP for a name but still need to specify the port. A reverse proxy like NPM solves that because you configure the routing and port once in NPM, and from then on http://immich.home.arpa just works.
Which DNS server is being used to resolve addresses can be much more nuanced, varying by device, operating system, browser, and network configuration. In most cases when a device joins a network, it receives DNS server information from the router. When you visit a website such as youtube.com or instagram.com, your device will typically send the DNS query to one of those configured DNS servers first. For local names such as immich.home.arpa, this only works if the DNS server knows about those records.
Certain apps and modern browser features are hindered without HTTPS. Even if you only plan on using them internally, HTTPS requires a TLS certificate. Let's Encrypt won't issue certs for private TLDs like .home.arpa or .lan and only validate domains that resolve publicly. So you're stuck generating and managing your own self-signed certificates across devices.
Buy a real domain. A numeric .xyz is under $1/year. Now Let's Encrypt happily issues a wildcard cert for *.131043.xyz via DNS-01 challenge. Configure your own private DNS with AdGuard Home to resolve *.131043.xyz to your local IP inside your network, while public DNS servers resolve it to your public IP outside. Same URL everywhere, same cert, every browser trusts its.
Let's consider how most home networks are set up.
Your ISP typically provides a single device that combines several networking functions into one unit:
Devices such as computers, phones, tablets, televisions, cameras, and game consoles connect to this device either through Ethernet or Wi-Fi.
Each device receives its own private IP address (for example, 192.168.50.20, 192.168.50.50, etc.) and can communicate directly with other devices on the same Local Area Network (LAN).
When a device needs to communicate with a service on the Internet, traffic is sent to the router. The router uses Network Address Translation (NAT) to translate the device's private IP address into the home's single public IP address assigned by the ISP.
As a result, dozens or even hundreds of devices can share one public Internet connection while appearing to the outside world as a single public IP address.
When you move beyond simply consuming Internet services and begin operating your own Internet infrastructure, you need a way for other systems to find you and communicate with you.
In practice, this usually requires:
This requires you to familiarize yourself with your Routers Web UI Dashboard. Typically the router occupies the first usable ip in the subnet which in home networks is something like 192.168.0.1. The routers provided by ISPs are usually capable of this feature, and you will find instructions online for your specific model.
Find the IP address of your Router:
ip route | grep defaultFor example, Navigate to http://192.168.0.1 and it's Port Forwarding page
You want to forward all traffic received for the WireGuard VPN (e.g default 51820/udp) to the Datacenter (e.g 192.168.50.10).
You want to forward all traffic received for port 443 for the Reverse Proxy NPM to the Datacenter (e.g 192.168.50.10). Port 80 is blocked at the ISP level, but that's okay because we shouldn't be exposing any unsecure http web apps outside our network. NPM is still able to issue certs using the DNS-01 challenge method.
Any device connected to the VPN joins a private, encrypted network and becomes reachable via a VPN-assigned private IP address. Your network can extend across multiple hops and geographic locations over the internet, while providing the necessary security to safely access remote systems as if they were local. Popular implementations are Wireguard, OpenVPN, IPSec, and Tailscale. In practice, Tailscale is the de-facto choice for beginners and hobbyists because it includes helpful features ready to use with minimal configuration.
Wireguard is a modern VPN solution designed to create secure encrypted connections between devices across separate networks. It's the core technology behind Tailscale, but without the higher-level conveniences. You gain more independence and direct control over how your network is structured, routed, and trusted. I like to think of WireGuard as the "Skeletal VPN" that you assemble yourself, and Tailscale as the “Readymade VPN" technology.
There is no built-in concept of a server or client and every connection is peer-to-peer. The Network Topology known as hub-and-spoke resembles a client-server model because think of the central hub as the "Dadacenter" where all your Readymade Contraptions are running and the outer spokes as your clients that want secure access. This is what we'll configure in the Wireguard section.
WireGuard contains the core cryptography and tunneling logic. Tools like wg-easy wrap it in a Docker container and apply a full set of forwarding and NAT rules automatically, so it just works the moment you spin it up. Doing it yourself strips that convenience away entirely. Nothing happens by default and you have to know exactly which netfilter/iptables rules you need. This is where the TOTAL CONTROL comes from, and also where the TOTAL PAIN comes from. The goal of this section is to get a firsthand idea of that struggle, but then just use wg-easy or Tailscale instead.
Doing it yourself means setting up a reliable address that devices can "phone home" to from anywhere on the Internet. This typically involves a domain name that is kept up to date through Dynamic DNS (DDNS), or a static Public IPv4 address. You must also configure your router or firewall to forward incoming WireGuard traffic (typically 51820/udp) to the private IP address of the WireGuard server running inside the Dadacenter.
51820/udp)sudo apt install wireguardEach peer generates a private key that never leaves the machine and a public key that is shared openly. Peers encrypt traffic using each other's public key, and only the matching private key can decrypt it. The private key stays on the machine it was generated on and goes into PrivateKey in the [Interface] block. The public key is what you share with the other peer and goes into PublicKey in their [Peer] block.
wg genkey | tee privatekey | wg pubkey > publickeyThe Host acting as the central hub will take on the role of a server by:
Creating a server config looks something like:
sudo vim /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.0.1/24
PrivateKey = <server-private-key>
ListenPort = 51820
[Peer]
# Linux client
PublicKey = <linux-client-public-key>
AllowedIPs = 10.0.0.2/32
[Peer]
# Windows client
PublicKey = <windows-client-public-key>
AllowedIPs = 10.0.0.3/32
[Peer]
# Mobile client
PublicKey = <mobile-client-public-key>
AllowedIPs = 10.0.0.4/32Starting the VPN session right now:
wg-quick up /etc/wireguard/wg0.confStarting the VPN session immediately on system boot:
sudo systemctl enable --now wg-quick@wg0When each client configures itself to communicate with the VPN server, AllowedIPs dictates which CIDR addresses are routed through the VPN tunnel. Routing only the VPN subnet and LAN (AllowedIPs = 10.0.0.0/24, 192.168.50.0/24) keeps regular internet traffic on the client's own connection and is known as "split tunneling". Routing everything through the server (AllowedIPs = 0.0.0.0/0), borrows its public IP and masks the client's own from the internet which is known as "full tunneling".
The client will need to obtain the Server's Endpoint (hostname/ip:port), Server's Public Key, and their own assigned VPN IP address beforehand. This primer assumes your client will be connecting from Android/iOS or GUI app, which helps setup configuration.
Wireguard has made available a GUI application for every Operating System:
Each client gets its own config where:
Create the client config (Linux):
sudo vim /etc/wireguard/wg1.conf
[Interface]
Address = 10.0.0.2/32
PrivateKey = <client-private-key>
DNS = 1.1.1.1
[Peer]
# Dadacenter server
PublicKey = <server-public-key>
Endpoint = 198.51.100.1:51820
AllowedIPs = 0.0.0.0/0Enable IP forwarding
Normally a host will not behave as a router because it only cares about messages that it is the destination for, dropping anything addressed to somewhere else. Enabling IP forwarding changes this default, telling the kernel to pass traffic through to another interface instead of discarding it.
sudo sysctl -w net.ipv4.ip_forward=1Add NAT/forwarding rules to the server config itself. This is the part wg-easy gives you for free, but a manual config lacks unless you add it. Without this, clients can reach 10.0.0.1 and nothing past it.
[Interface]
Address = 10.0.0.1/24
PrivateKey = <server-private-key>
ListenPort = 51820
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
# Linux client
PublicKey = <linux-client-public-key>
AllowedIPs = 10.0.0.2/32When the VPN goes up, PostUp = iptables -A FORWARD -i wg0 -j ACCEPT is needed to allow traffic arriving from the WireGuard interface to be forwarded onward to its destination, rather than dropped by the host. iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE is needed to rewrite that traffic's source address to the host's own IP before it leaves through eth0, so replies from the internet know how to find their way back to a private VPN client address that the outside world has no route to otherwise. Without them, traffic from connected clients either get silently dropped at the host before it ever leaves, or it leaves with a private source address the internet has no way to send a reply back to. PostDown simply undoes those changes when the VPN is brought down.
WireGuard-Easy allows you to manage the WireGuard VPN through a Web-based Admin UI. Instead of manually creating keys, editing configuration files, and troubleshooting firewall rules, new VPN clients can typically be created with a few clicks from the web interface. It automatically generates client configurations and QR codes, allowing phones, laptops, and tablets to connect with minimal setup.
volumes:
etc_wireguard:
services:
wg-easy:
environment:
# Optional:
# - PORT=51821
# - HOST=0.0.0.0
- INSECURE=true
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
networks:
wg:
ipv4_address: 10.42.42.42
ipv6_address: fdcc:ad94:bacf:61a3::2a
volumes:
- etc_wireguard:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51820:51820/udp"
- "51821:51821/tcp"
restart: unless-stopped
cap_add:
- NET_ADMIN
- SYS_MODULE
# - NET_RAW # ⚠️ Uncomment if using Podman
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.all.forwarding=1
- net.ipv6.conf.default.forwarding=1
networks:
wg:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 10.42.42.0/24
- subnet: fdcc:ad94:bacf:61a3::/64After docker compose up -d, the Admin UI is accessible in the example network at http://192.168.50.10:51821/

You have to specify what address and port gets baked into every generated peer config so peers can initiate a connection to your WireGuard server. This requires:

Generating new peer configurations and distributing them becomes easier. Every new peer created will allow you to export a file to share for other Wireguard Clients to quickly import and use. The same confioguration file is encoded into a QR Code that your WireGuard VPN smartphone app will quickly recognize. Then connecting and disconnecting to your VPN is a simple as toggling a switch on/off.
Tailscale will quickly set you up with a secure peer-to-peer mesh VPN using the WireGuard protocol and a Coordination Server to handle Key Exchange between Peers, Identity and Authenication through OAuth/SSO, Fallback Relay coordination (DERP Servers), and Endpoint discovery. Tailscale is known as "open-core" because the Tailscale clients (the daemon that runs on each node) are open source, but the coordination server (controlplane.tailscale.com) is proprietary and provides a few more capabilities and conveniences. Headscale is an alternative community made open-source coordination server to run on your own infrastructure.
A Reverse Proxy is an "intermediary server" that sits between clients and the actual services they are trying to reach. It can be used a few different ways such as a Load Balancer to spread traffic across multiple duplicate servers, a Cache for static content to reduce load on servers, and Transport Layer Security (TLS) so backend servers don't have to handle encryption themselves. They allow multiple services (e.g Immich, ArchiveBox, Nextcloud) to be accessed via one port (443) using subdomains. They obfuscate the IP Addresses of the servers behind them, hiding the real backend IPs from clients.
Nginx Proxy Manager (NPM) allows you to manage Nginx through a Web-based Admin UI.
When I first set up NPM, I did what everyone does which is to publish a port on every container and point NPM at the <host IP>:port - it works but it's less secure, and clutters up your host's firewall ports. This is because the simplest approach to get something running is to create a docker-compose.yaml and run docker compose up -d, which automatically places containers on a project-specific default network and encourages publishing ports to the host.
The cleaner move is to create a shared proxy Docker network for NPM and attach only the containers that need forwarding to it, while keeping the rest on their default project network. This only requires you to add a few lines to each docker-compose.yaml. A similar approach is encouraged in the npm documentation, but we don't add ALL containers to the shared network.
docker network create npm_networknpm_network.services:
app:
image: 'jc21/nginx-proxy-manager:2.15.1'
restart: unless-stopped
environment:
TZ: "Australia/Brisbane"
ports:
- '80:80'
- '81:81'
- '443:443'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
- default
- npm_network
networks:
npm_network:
external: trueAfter docker compose up -d, the NPM Admin UI is accessible in the example network at http://192.168.50.10:81/
docker-compose.yaml to attach the main container to both the proxy network and the project-specific default network. You will need to add a networks: block declaring the external network, and then specify which container to use it. You must also include - default explicitly, so that the container is attached to both their default project network and the npm_proxy network. You will also no longer need to Publish ports because npm is able to directly route traffic using the proxy network. For example, the Immich compose becomes:name: immich
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
volumes:
- ${UPLOAD_LOCATION}:/data
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
# ports: # No longer need to Publish Ports to the Docker host
# - '2283:2283' # NPM can route using http://immich_server:2283
depends_on:
- redis
- database
restart: always
healthcheck:
disable: false
networks:
- default # internal immich network (db, redis, ml)
- npm_network # shared network with NPM
immich-machine-learning:
container_name: immich_machine_learning
......NO CHANGE
redis:
container_name: immich_redis
......NO CHANGE
database:
container_name: immich_postgres
......NO CHANGE
volumes:
......NO CHANGE
networks:
npm_network:
external: true
Use the Navigation Menu to go to the Proxy Hosts page (Hosts > Proxy Hosts) and Add Proxy Host. Based on our immich example we will make https://immich.12419251.xyz available on the internet and route our traffic to our immich_server container.
We use the wild certificate supporting all subdomains *.12419251.xyz for immich.12419251.xyz and


Use the Navigation Menu to go the Certificates page and Add Certificate > Let's Encrypt via DNS. Whichever domain registrar or DNS provider you end up with will provide an API key for their DNS service. NPM needs this to complete the DNS-01 challenge when issuing wildcard certificates (*.12419251.xyz) from Let's Encrypt. Why a wildcard? Without one, you'd need to request a separate certificate for every subdomain (e.g immich.131043.xyz, frigate.131043.xyz, and so on. A wildcard cert covers all of them with a single issuance.
Reverse Cloud Tunnels make your services available without publicly exposing ports on your server. They are a work-around for restrictive firewalls (e.g ISPs, Corporate, CGNAT) by leveraging outbound traffic leniency on tcp/443 (HTTPS) to establish a persistent connection to a cloud relay like Cloudflare Edge Server that accepts and forwards inbound public traffic back through the pre-established tunnel to your local service.
Cloudflare Tunnels, Tailscale Funnels will automatically handle HTTPS/TLS on your assigned domain but will vary in how much control you have over that domain name. Cloudflare allows you to use your owned domain (e.g https://123456789.xyz) but also provides ephemeral temporary domains like https://adjective-noun-verb-word.trycloudflare.com. Tailscale Funnels is locked to *.ts.net and only assigns your service to hostname.tailnetname.ts.net/appname (e.g https://dadacenter.tail6234.ts.net/immich).
In the Nginx Reverse Proxy example, we forwarded requests to the Dadacenter at 192.168.50.10:443 through an open port on the home router. A reverse tunnel eliminates that requirement. Instead of opening a port, a process on your LAN maintains an outbound connection to a VPS relay, and clients access services on your LAN from there. Since these tunnels expose one port per tunnel, without Nginx or something like it, you'd need a separate tunnel for every service. For the following examples, we've already set up Nginx Proxy Manager, so we only use 1 tunnel on port 443/tcp and let NPM multiplex traffic to its destination.
To use Cloudflare Tunnel with your own domain, you first need to delegate DNS to Cloudflare by pointing your registrar's nameservers (e.g Namecheap, Porkbun, etc) at Cloudflare's. Then setting up Cloudflare to become the authoritative DNS.
cloudflared - The Cloudflare Tunnel Daemon and have it automatically hook into cloudflare infrastructure. You can run it as a systemd unit or as a container.docker run cloudflare/cloudflared:latest tunnel --no-autoupdate run --token *******.12419251.xyz and the Service URL is the address of NPM running on the dadacenter https://192.168.50.10:443
If you are already using Tailscale as your VPN, Tailscale Funnel becomes the shortcut from private service to public URL. It's much simpler to use it as intended. It is simplest when used as intended: exposing one local service through one public Tailscale hostname.
A Funnel URL looks like this:
https://dadacenter.tail6234.ts.net/
Apps like Frigate, Home Assistant, Immich, and many others can sometimes be placed under subpaths:
https://dadacenter.tail6234.ts.net/frigate
https://dadacenter.tail6234.ts.net/immich
https://dadacenter.tail6234.ts.net/homeassistant
But this is where things get fragile.
Many self-hosted apps assume they live at the root of a site. Their frontend code, API calls, login redirects, websocket connections, video streams, and static assets may point to paths like:
/api
/ws
/live
instead of:
/frigate/api
/frigate/ws
/frigate/live
Some applications support being hosted under a subpath by letting you configure a base URL, public URL, root path, or URL prefix. When that option exists, the app knows to generate paths like /frigate/api instead of /api, so subpath routing can work cleanly.
You need your domains authoritative dns to have an A Record pointing any subdomain *.12419251.xyz to the ipv4 address of your vps (e.g 198.51.100.25)
With frp you run frpc on the client and frps on the relay vps.
sudo useradd --system --no-create-home --shell /usr/sbin/nologin frpssudo vim /etc/systemd/system/frps.serviceWe want to access our immich server with https://immich.12419251.xyz instead of https://immich.12419251.xyz:1443. Port 443 is privileged port, which means only the root user or processes with administrative privileges can listen on them. So we create the frps user and invoke AmbientCapabilities andCapabilityBoundingSet in the frps.service to work around this.
[Unit]
Description=frp server
After=network.target
[Service]
Type=simple
User=frps
ExecStart=/opt/frp/frps -c /opt/frp/frps.toml
Restart=on-failure
RestartSec=5s
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.targetbindPort = 7000sudo systemctl daemon-reload
sudo systemctl enable --now frps
sudo systemctl status frpssudo useradd --system --no-create-home --shell /usr/sbin/nologin frpcsudo vim /etc/systemd/system/frpc.service[Unit]
Description=frp client
After=network.target
[Service]
Type=simple
User=frpc
ExecStart=/opt/frp/frpc -c /opt/frp/frpc.toml
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.targetserverAddr = "198.51.100.25" # ipv4 address of vps running frps
serverPort = 7000 # the bindPort setup in the relay vps frps
[[proxies]]
name = "npm-tcp"
type = "tcp"
localIP = "127.0.0.1" # our loopback localhost address since we are running npm on the same host
localPort = 443 # the port npm is using for https
remotePort = 443 # the port clients have to use, recall the port 1443 avoidance in the frps sectionsudo systemctl daemon-reload
sudo systemctl enable --now frpc
sudo systemctl status frpcLarge Language Models (LLMs) are the core technology augmenting how we read, write, search, and reason. The proprietary frontier models such as the latest GPT, Claude, Gemini, and Grok represent the avant-garde of this technology. Each one is accessed remotely through metered APIs, and billed by the token for every word they read and every word they write. LocalLLMs are powerful open-weighted models, meaning that the labs that trained them released the weights publicly, and you can run them on your own hardware. Hugging Face has become the registry for open-weights LLMs, hosting models such as DeepSeek, Qwen, Llama, Gemma, and Mistral. Anyone can download them, and enthusiasts running their own LLMs are demonstrating that gaming GPUs can be enough in certain use cases to assist in vibe-coding and putting the model to work autonomously. Harnessing these models in the right environment, equipped with tools to use, memory to draw on, and a workspace to act in, allows them to punch up in their capabilities.
Initial setup requires 3 swappable layers, often bundled together.
The three tools below each handle these in different ways. Ollama and Docker Model Runner bundle all three into a single command. llama.cpp is the bare inference engine the other two are built on; running it directly means assembling the model, the engine, and the gateway yourself.
Whether you are setting up an LLM locally or tapping into a subscription or token-based cloud provider, what you want is an OpenAI-compatible API that can serve as the engine driving your autonomous agents. LLMs primarily started as a request-and-response interaction but have quickly evolved into feedback loops where the response also becomes the request. OpenClaw became the first Readymade self-hosted agentic platform demonstrating that a personal AI agent could live entirely on your own hardware and be customized to the specific shape of your own absurdity.
Readymade agent platforms like Claude Code, Codex, Hermes, OpenClaw, OpenCode, and similar tools are built around the same basic idea: give a language model a workspace, a goal, tools, memory, and permission to act. The model stops being only a chatbot and becomes a cybernetic loop that observes, plans, edits, runs commands, reads the result, corrects itself, and continues.
Although each platform has its own interface and philosophy, they tend to share a few common layers:
Markdown Interface — Markdown is the common surface between human and agent: instructions, plans, notes, docs, summaries, and task outputs all become readable, editable text.
Prompt Hierarchy — Agents are shaped by layered instructions: system rules, project rules, skills, and the user’s immediate request. This determines what the agent should prioritize.
Workspace Context — The agent is attached to a real project or environment. It can inspect files, search code, read documentation, notice conventions, and adapt its work to the existing system.
Tool Use — The model can reach outside itself through shell commands, file edits, browsers, APIs, package managers, test runners, databases, and local services.
Skills and Plugins — Reusable bundles of instructions and tools let agents specialize. A skill might teach browser testing, image generation, code review, deployment, or a project-specific workflow.
Action Loop — The agent observes, plans, acts, reads the result, corrects itself, and continues. This feedback loop is what separates an agent from a chatbot.
Permissions and Sandboxing — Because agents can affect real systems, platforms usually control access to files, networks, secrets, installs, commits, deploys, and destructive commands.
The important shift is that these systems make agency itself into readymade logic. You are no longer writing every command by hand. You are steering an evolving substrate where an LLM can reach downward through tools into the operating system, the repository, the browser, the container stack, and eventually the rest of your infrastructure, and beyond.