Skip to main content

About The Engineer

The human story behind the engineering: how I built a fast, cost-effective real estate site for my wife in Rust and Dioxus, server-rendered, running on a $100 N95 mini PC in my house.

Call Joe
Joe Wennesheimer

About Me

Hi, I'm Joe. I've lived in Green Bay most of my life, surrounded by family, before my wife and I made the jump up to Crivitz a decade back. We moved here for the slower pace and to be closer to the water. I wanted to be able to fish after work without driving an hour, and now I do exactly that.

My path hasn't been a straight line. I started out studying Kinesiology with dreams of athletic training, then spent time in construction learning how to build things with my hands. Eventually, I realized I enjoyed solving complex problems more than anything else, which led me from hammers to keyboards. Today, I run a tight household with two dogs, two cats, a bearded dragon, and about 20 fish. I'm also a dad who loves playing RPGs and base-building games with my daughter, and I'm still grinding on Old-School Runescape after 22 years.

People often describe me as the "rock" of the group. I tend to be the calm one when things get chaotic, though sometimes I solve problems a little too quickly. I have a habit of offering solutions when people just want to vent. That problem-solving mindset extends to everything I do, from fixing cars and doing home repairs to helping my wife navigate business decisions. In fact, our partnership works because I handle the strategy and technical logic while she manages the people and legal side. Seeing her finish her first year in the top 8% of agents in our county is something we're both incredibly proud of.

I'm a pretty direct person. I value honesty and clarity over corporate fluff because I believe we waste enough time already. If there's a problem, I'll tell you plainly what I see and how we can fix it. It's not about being tough; it's about respecting everyone's time and getting to the truth so we can move forward.

I build software because it's a craft I'm genuinely good at, and because it funds a life I actually want. Eight hours of focused effort, then home. Weights at 5 AM, evenings on the water or playing something co-op with my daughter. Both halves matter to me, and I've found I do my best work when each one gets its share.

If you're looking for someone who takes ownership, communicates clearly without the noise, and genuinely cares about building things that last—both technically and personally, I think we'd get along well.

Why I Built This

My wife, Alyssa, needed a website when she started her real estate career. Most agents use templated platforms—and honestly, I couldn't stand the idea of her being stuck with something generic, slow, and dependent on someone else's code.

For me, this wasn't just about having a nice-looking page. I didn't want the burden falling on her to debug why something wasn't working, or to figure out design decisions while also running her business. I wanted us to own everything. No licenses, no monthly fees, no waiting on a vendor to fix an issue. Just a system we built ourselves, where every piece could be understood, adjusted, and secured.

There was also a growing frustration in how much of modern web development has become bloated and fragile. We started down the path using Angular, but soon after, a series of supply chain attacks hit the NPM ecosystem hard. I found myself worried about pulling in packages that could compromise the site. The whole thing felt like it was built with toothpicks, and I realized I didn't want to inherit that problem forever.

So I stripped it back. Instead of starting with an entire house, I decided to start with a shed. Build it lean, build it stable, and let it grow naturally from there. The first version was vanilla HTML, CSS, and JavaScript—no build step, nothing to audit, all of it hosted locally on hardware I secured myself. Web crawlers prefer simple HTML anyway, and from a business standpoint, faster load times and better SEO mattered more than flashy frameworks.

The shed grew. Hand-writing every page meant every layout fix had to be made in a dozen files, and that started costing more time than it saved. So the site moved to Rust and Dioxus, which turned out to be the version of this idea I actually wanted: components and a router like a real framework, but rendered to plain HTML on the server before it ever reaches a browser. Crawlers still get finished markup on the first request. The whole site is now one language, one binary, and one dependency tree I can actually read.

I spent most of my time laying the foundation: securing the N95 mini PC, building a deployment pipeline that ships on every push and rolls itself back when a release comes up unhealthy, making sure the theme stayed consistent across every screen size. I wanted to build something that could last, not something that just shipped for a pitch deck.

I'm also aware that most of this is invisible. I've stayed off social media because I value my privacy, and most of my friends came up through construction—there's no reason they'd know what goes into spinning up a Proxmox host, locking down containers, running traffic through a Cloudflare tunnel, and deploying a full-stack app from scratch. People can see the website. They can't see the work underneath. This page is where that part lives.

Will it ever be done? Probably not. That's the engineer in me. There's always room to improve, always something to refine. But Alyssa is genuinely impressed, and I've had other people reach out asking if I can build their sites too. That's validation enough.

Why I Built It This Way

If you strip away the code and talk about values, there are five principles that guided every decision on this project. None of them were flashy — they're just things that matter when you want something to last.

Start Lean, Scale Later

I started hosting on Azure Web Apps because static sites are free there. But once I needed a backend, I hit a wall: I didn't want to risk an infinite loop or something wild and get hit with a $10,000 bill from cloud providers. My next idea was using a Raspberry Pi I had lying around, but then I'd have to deal with ARM architecture quirks. So I landed on the N95 Mini PC, which is reliable enough to boot after power loss, powerful enough for the load, and simple enough that I can manage it myself. No AWS, no GCP, no vendor lock-in. Just hardware I control.

Trust the Compiler, Not Me

I picked Rust because I like the "pay for it upfront" concept. The language trusts the compiler over the developer, and honestly, I trust the compiler more than myself. Am I a Rust expert? Not really. I go through spurts where I'm excited to write in Rust, struggle through building something, and then set it down for a bit. But when it compiles, the chances of it crashing in production are slim. That peace of mind means I don't have to worry about runtime surprises eating into my time off.

Keep It Simple On Purpose

There wasn't one moment where I decided "frameworks are bad." It was smaller things compounding: the supply chain attacks, the endless dependency updates, the feeling that I had to double-check the language instead of the language double-checking me. Those added up until starting from scratch felt easier than auditing what I'd inherited. So I did, and I learned the actual lesson the long way—what I wanted wasn't the absence of tools, it was a small dependency tree I could hold in my head. Vanilla was one way to get that. Rust and Dioxus turned out to be a better one: I got components and a router back, and the whole dependency list still fits on one screen. Simple isn't the same as primitive.

Security Is About Realism, Not Paranoia

I wouldn't call myself paranoid about security. At work, we never dealt with major breaches. And sometimes I think people take it too far. Most people don't have iron bars on their windows. We acknowledge that if someone wants in, they'll find a way. We just try to make it hard enough that they give up before trying. If the security is so heavy that I can't do my own work or access my own systems, it's pointless. This approach came from thought experiments and research, figuring out what actually matters and implementing that, nothing extra.

SEO First, Everything Else Second

Truthfully, SEO was the biggest driver behind every frontend decision here. I spent hours researching how to make SEO work with client-rendered web apps, and it always came out overly complicated—a crawler that has to run your JavaScript before it can see your content is a crawler you're gambling with. Server rendering removes the gamble: the HTML is finished before it leaves the machine, structured data and all. No rendering delays, nothing to wait on. I want Alyssa to be the first result when someone searches for real estate in Crivitz. We're not there yet, but that's the end game, and every technical choice on this page traces back to it. Technology should serve the business, not the other way around.

The takeaway: Every choice here was made for longevity, transparency, and ease of ownership. Not because they're trendy, but because they respect the end user's time and the owner's control.

The Architecture Underneath

Everything powering awrealty.homes runs on a single N95 mini PC sitting in my house. Four cores, 7.5GB of RAM, no cloud provider, no managed services, no monthly infrastructure bill. Nothing runs as root, and the account that deploys the site is not the account that runs it. Here's how it all fits together.

The Host: Proxmox & One Container

The N95 runs Proxmox, and the whole site lives in a single unprivileged LXC container: Debian 13, 3GB of RAM, all four cores available to it. Inside that container are Nginx, the Cloudflare Tunnel client, and the application itself—one Rust binary that serves every page.

It used to be three containers. A 256MB Alpine box held the static frontend, a 1GB Alpine box ran the backend service, and a third ran self-hosted monitoring. Splitting them up felt like the disciplined choice at the time, and it was mostly ceremony: three sets of updates, three firewall postures, and a network hop between two halves of the same application. When the site moved to server-side rendering, the frontend and backend stopped being separate things, and keeping them apart stopped making sense.

So I consolidated. The interesting part is the number: one container running the entire site uses less host memory than the three it replaced. The old ones are stopped rather than deleted—a stopped container is a one-command rollback, a destroyed one is a rebuild, and disk is cheaper than certainty.

The Application: One Rust Binary

The site is a Dioxus app written entirely in Rust, and it ships as a single binary. That binary does both jobs: it renders every page to finished HTML on the server, and it serves the compiled WebAssembly bundle that takes over in the browser afterward for the interactive pieces—the theme toggle, the nav, the carousels. A visitor gets complete markup on the first response and never waits on JavaScript to see content. A crawler gets the same thing and never has to run any.

The dependency list is short on purpose: Dioxus, Axum, Tokio, Serde, Tower-HTTP, and Tracing. That's the whole tree. Axum and friends are feature-gated behind the server build, so the WebAssembly bundle never compiles them and never carries their weight to the browser.

There's no database yet, and that's deliberate rather than unfinished. Nothing on the site currently needs to remember anything between requests, and a database I don't need is a backup I have to take, a migration path I have to maintain, and one more thing that can be down at 2 AM. When the contact form lands it'll be SQLite through sqlx—queries checked at compile time, and the entire database is one file, which makes backing it up completely uninteresting. That's the highest compliment I can pay a backup strategy.

The Network: Cloudflare Tunnel, Nginx & Tailscale

The networking layer is designed so that nothing is exposed to the public internet directly. When someone visits awrealty.homes, the request flows through a Cloudflare Tunnel—a secure outbound-only bridge that lets Cloudflare talk to the N95 without ever opening a port on my home router or revealing my home IP. Cloudflare terminates TLS at the edge, so Nginx inside the container listens on plain HTTP and acts as the traffic cop: hashed assets come straight off disk with a one-year cache, and everything else goes to the Rust binary for rendering.

There's no API subdomain anymore. When the frontend and backend are the same program, server functions register their own routes on the main app, same origin—so the extra hostname was just a DNS record with a story attached. Deleting it removed a moving part and a CORS policy in one go.

For management, I use Tailscale to create a private, encrypted network exclusively for my devices. This lets me SSH into the N95, deploy code, and manage containers from my laptop without exposing SSH to the world. The result is zero inbound ports open to the internet. Everything either goes through the Cloudflare Tunnel (public traffic) or Tailscale (my traffic).

The Pipeline: Ship On Push, Roll Back On Failure

One GitHub Actions workflow handles the whole deploy. Push to main and it builds, connects to the box over Tailscale, rsyncs the release up, and restarts the service. I never touch the server by hand.

Each build lands in its own directory named after the commit, and going live is a symlink swap—a single rename syscall, so there's no instant where the live path points at nothing. Releases are never modified in place, only pointed at, which is what makes rolling back immediate instead of a re-copy. The previous good release keeps its own symlink, so undoing a bad deploy by hand is one command even if GitHub and my network are both unavailable.

Then it checks its own work, and this is the part I care about most. After the restart, CI polls the running app and verifies real behavior through Nginx: the homepage renders, robots.txt is served, the sitemap comes back non-empty, and an unknown URL actually returns a 404. If any of that fails, the pipeline points the live symlink back at the last good release, restarts, and polls again to confirm the old version is genuinely answering. Then it still reports the build as failed, because nothing can un-fail the step that already failed. A red build and a live site is the correct outcome.

I built it that way because I watched the alternative happen. A sibling project's deploy script printed "Deployment Successful" unconditionally—it copied the binary under the wrong filename and restarted a service that kept running the old one. It shipped nothing for three months and nobody noticed, because every single deploy was green. A restart command exiting zero tells you a process started. It tells you nothing about whether your site works. So now the pipeline has to prove it.

Resilience & Backups

Keeping the site alive matters more than I'd like to admit. The N95 sits on an uninterruptible power supply, so if the power drops there's a buffer before anything shuts down, and it shuts down gracefully rather than dying mid-write. The BIOS is set to auto-boot when power returns, Proxmox brings the container back up on its own, and the site comes back without me. The auto-boot was the part that fought me. It lives in the BIOS under "State After G3"—G3 being the ACPI term for "mechanical off," which is why searching the menu for anything about power loss turns up nothing. Its value is a bare integer with no legend explaining what any of the numbers do. Set it wrong and the failure is completely silent: the machine just stays off after an outage, and nothing logs it, because nothing is running to log it. I know it works because I've cut the power and watched the whole thing come back, more times than I can count. Untested recovery isn't recovery.

Monitoring is external only, and that's a correction rather than a shortcut. I used to run Uptime Kuma on the same host it was watching, which sounds fine until you think about the failure that actually matters: if the house loses internet, the monitor loses internet too, and the thing that was supposed to tell me the site is unreachable is now equally unreachable. A monitor that can't report the outage it's most likely to encounter isn't monitoring. UptimeRobot checks the live site from outside my network instead, and it looks for the words "AW Realty" in the response rather than just a 200—because this codebase has already produced pages that returned a perfectly healthy status code while being visibly broken.

Backups run in two stages. Proxmox snapshots the container nightly and keeps a week locally, then a script ships those snapshots to cloud storage at 5 AM with a 30-day retention window. Writing locally first and copying second is the important detail: pointing the snapshot job straight at a network mount means a stalled connection can interrupt it mid-write and leave the container locked, which is exactly the state I once found every container on this host in.

But the real lesson wasn't about backup software. One job died mid-run and left a lock file behind, and every night afterward the job failed on the lock rather than on the original problem. It ran that way for six weeks before I caught it. The trigger was a genuine fluke, the kind of thing I may never see again—and that's exactly why it went unnoticed for so long. Routine failures get spotted quickly. Anomalies are the ones that sit there quietly, because nothing about the system looks wrong until you go looking. So the fix wasn't a better backup tool, it was making failure loud: both alerting paths now mail me, and both were verified by deliberately breaking them. An untested alert is just a comment in a config file. A backup you haven't proven you'd hear about failing isn't a backup, it's a hope.

What I'd Change

If I'm being honest, frontend work isn't my strong suit. I'm a backend guy. I like seeing data move, watching systems respond, getting the result I want. Design doesn't come as naturally, and I've had to work at it here in a way I haven't had to work at the infrastructure.

This site has gone through several iterations, and the current one won't be the last. That used to bother me. It doesn't anymore—every rewrite on this list happened because I'd learned something specific that the previous version couldn't take advantage of, and the versions have gotten measurably better each time: fewer moving parts, less memory, faster pages. Not being done is what it looks like when you're still paying attention.

LayerTechnologyWhy
FrontendRust + Dioxus (SSR + WebAssembly)Finished HTML on the first response. Crawler-friendly. Maximum SEO.
BackendRust (Axum + Tokio), same binaryCompiles safe, crashes rarely. One language end to end.
DatabaseNone yet — SQLite via sqlx when neededNothing to persist yet. Queries checked at compile time; one file to back up.
Reverse ProxyNginxHashed assets straight off disk. Everything else to the app.
TunnelCloudflare TunnelZero open ports. Home IP stays hidden. SSL handled automatically.
VPNTailscalePrivate access for deploys and management. No public SSH.
InfrastructureN95 mini PC + Proxmox, one unprivileged LXC$100 hardware, full ownership, constrained by design.
OS (App Container)Debian 13Boring on purpose. The build container matches it, so the binary links cleanly.
CI/CDGitHub Actions + rsync, atomic symlink releasePush to deploy. Health-checked, with automatic rollback.
MonitoringUptimeRobot (external, keyword check)Off-network, so it can report the outage that matters. Alerts to phone.
BackupsProxmox snapshots + nightly offsite copy7 days local, 30 days offsite. Failures are alerted, and the alerts are tested.
PowerUPS + BIOS auto-bootPower backup and auto-recovery on power return.

Equal Housing Opportunity. Alyssa Wennesheimer, REALTOR® — NAR & RANW Member — License #94-113391. Lisa Stradel Realty — License #91-905461.