Cloudflare Tunnel (formerly Argo Tunnel) is a secure tunneling service from Cloudflare that exposes local HTTP and TCP services to the Cloudflare edge by creating an outbound connection from a host to Cloudflare's network. It is implemented by the open-source cloudflared client/daemon, supports named tunnels and ingress rules, and integrates with Cloudflare Zero Trust / Access for authenticated routing and access control. The service is distributed by Cloudflare and the cloudflared client is published on Cloudflare's GitHub.
Git is an open-source, fast, scalable, distributed revision control system. It stores project changes as versioned commits and provides repository history, diffs, branching, isolated work trees, and rollback capabilities for reviewing or reverting changes. The project provides both high-level version-control operations and access to its internal mechanisms. It is covered primarily by the GNU General Public License, with some components under compatible licenses, and was originally written by Linus Torvalds with contributions from a wider developer community.
Hostinger is a web hosting provider operated by Hostinger International Ltd that offers shared hosting, VPS and cloud hosting, domain registration, and website builder tools. It markets services globally through localized sites and partner/affiliate links.
just is a cross-platform command runner for saving and executing project-specific commands. Commands, called recipes, are defined in a justfile with syntax inspired by make and invoked with commands such as `just deploy` or `just test`. Unlike make, just is a command runner rather than a build system. Recipes can accept arguments and dependencies, use variables and built-in functions, load `.env` files, run through configurable shells or other interpreters, and be organized with imports and modules. It supports Linux, macOS, Windows, and other Unix-like systems, provides shell-completion scripts, and can be installed through package managers, pre-built binaries, Cargo, or from its source repository.
K3s is a lightweight, certified Kubernetes distribution for production workloads in resource-constrained, remote, unattended, and IoT or edge environments. It packages Kubernetes as a single binary smaller than 70 MB, reducing installation, runtime, and update dependencies. A K3s cluster uses server and agent nodes: the server writes a kubeconfig and agents join it with the server URL and node token. K3s supports x86_64, ARMv7, and ARM64, and is distributed as a CNCF Sandbox project.
Nix Flakes is a Nix configuration and packaging mechanism that uses a `flake.nix` file in an application repository to define the application as a Nix package. The resulting package can be imported as a dependency by another flake, such as a VPS configuration. It operates within the Nix ecosystem, whose package manager is designed for declarative, reproducible system configurations and isolated package builds.
NixOS is a Linux distribution built around the Nix package manager and Nixpkgs. It describes laptops or server fleets through declarative configuration, builds system changes before activation, and stores generations that can be used to roll back to earlier configurations. The Nix ecosystem emphasizes reproducible package builds, isolated dependencies, and upgrades that do not leave packages in an inconsistent state.
NixOS VPS is a starter NixOS configuration repository for deploying and managing an x86_64 VPS, originally targeting the Hostinger NixOS template. It uses a Nix flake and `just` recipes to bootstrap encrypted SOPS/age secrets, build the system locally or on a separate Linux builder, transfer the resulting closure over SSH, and activate it on the server. The configuration creates an administrative account, manages SSH access, and supports deploying Docker or Podman containers and systemd services through NixOS modules. Optional modules provide Tailscale access and locally managed Cloudflare Tunnel publishing. Secrets remain encrypted in the Git-backed repository and are decrypted on the VPS; the repository also documents multi-server configurations, macOS and ARM build setups, trusted-build considerations, and service deployment. It is distributed as source code under the repository's GitHub project; the configuration must be adapted when used with a provider or VPS template other than the documented Hostinger setup.
Podman is an open-source container engine used to run OCI containers, including as the backend for the NixOS OCI containers module. It is compatible with container-focused tools and workflows; the official site documents support in Visual Studio Code, Cirrus CLI, GitHub Actions, and Kind for local Kubernetes clusters. Podman Desktop provides a graphical interface for using Podman.
Tailscale is a networking service for creating private WireGuard-based networks between a user's devices and servers. It can provide remote access over SSH and route traffic through an authorized device configured as an exit node, such as a local Mac, so external websites see that device's network address rather than the server's data-center address. The project includes the open-source `tailscaled` daemon and `tailscale` command-line tool, with the daemon running on Linux, Windows, macOS, and to varying degrees on FreeBSD and OpenBSD. The repository also supplies code used by Tailscale's mobile applications; platform-specific GUI wrappers and hosted-service components are separate. Packages are provided for several distributions, and the project uses WireGuard with authentication features including 2FA, OAuth, and SSO.
Searchable transcript of Why I'm no longer using Docker (on a VPS) — Dreams of Code (16:54). Search for a phrase, then click its timestamp to jump straight to that moment in the video.
Captions sourced from the original video on YouTube, published by Dreams of Code. The video, its captions and all related intellectual property remain the property of their respective owners; AINotes claims no ownership. Provided for research, accessibility and search — see the Transcript Notice and Copyright Policy.
00:02 After a decade of deploying applications to a VPS, I'm no longer doing so using Docker. Wait, what? This includes using things like Docker Compose platforms like Dock Ploy, and even using it to build containers. How did this happen? Well, it all began with a fundamental change I made to my VPS setup, one that I had been wanting to make for just over a year.
00:32 >> [music] >> Thanks to support being added by my VPS provider and the sponsors of today's video, Hostinger, I've now been running Nixos on all of my production instances for the past couple of months. However, this change by itself doesn't explain why I'm no longer using Docker or any of its counterparts such as Podman for deploying my applications, especially considering the fact that Nixos is perfectly capable of running Docker.
00:59 In fact, I would even argue that Nyxos is one of the best ways of declaratively managing Docker containers. So, why am I no longer using Docker? Well, it all came down to the fact that adding Docker into my NixoS workflow was actually creating more problems than it was solving. To show what I mean, let's take a look at what my workflow actually is for redeploying an application to a VPS following a change.
01:22 When using Docker, this involves first pulling down the changes, building a new Docker image, giving it a unique tag, and then pushing it to a container registry. Once this new image has been published, I can then deploy it on Nexos by opening up my configuration, modifying the existing container definition to reference this new image, and then running the following command to rebuild and apply the configuration onto my VPS.
01:44 After which, the new version of the application will be running. For anyone who's used Docker in production, this workflow should feel rather familiar. And whilst it has a few steps, it's a very simple way to deploy an application to a VPS. However, when compared to deploying an application on Nyxos without using Docker, it can in fact feel rather tedious.
02:05 To redeploy the exact same application, I only need to use two commands. The first to update the application in my configuration to the latest commit and the second to redeploy it. When compared to Docker, this approach not only has far fewer steps, which means less room for error, but it also removes the need for third party infrastructure such as a container registry, as I'm able to deploy directly to the VPS from my local machine.
02:28 All of which results in a much simpler workflow for deploying my applications to a VPS. Now, I know what you're thinking. Why not just automate this? Well, whilst automation would simplify this workflow from my perspective, it wouldn't change the fact that these steps were still being performed, which not only meant I would need to continue using a registry, but because of the potential errors that this workflow brings, it would continue to undermine some of the main benefits of using Nyxos in the first place.
02:56 To show what these benefits are, I've gone ahead and created a brand new VPS instance from Hostinger running Nyx OS. Before I'm able to deploy an application to it, the instance first needs to be configured, which means adding in user accounts, hardening SSH, and installing any of the services that I need, such as Tailscale, Cloudflare Tunnel, and of course, Docker.
03:17 In the past, I would perform this configuration imperatively, either through typing out each command by hand or using a configuration management tool. With NixoS, however, this all takes place declaratively inside of a single configuration, one that I've defined already. This means I'm able to configure my instance by cloning down my existing configuration and then running the following command which will build the system from this configuration on my local machine, transfer it over to the VPS and then activate it.
03:44 After which the instance will be configured exactly how I've defined. If I want to make a further change to the system, such as installing a new package, rather than doing this on the instance itself, I instead open up my configuration, make the relevant change and then I can reapply it using the same command. Although because I have a just file set up in this repo, I can instead use the just deploy command which is a little more convenient.
04:06 Once the new version of the system is activated, the package will then be available. On first impressions, this approach may feel somewhat similar to using a tool such as anible. However, there is in fact a pretty substantial difference. Rather than modifying the existing system in order to bring it into alignment with the configuration, Nixos instead attempts to build the entire system first from this configuration before then activating it on the machine.
04:32 This means if I make a change to my configuration that will cause the build to fail, then my VPS will be left unchanged. One major benefit of this behavior is that it reduces the risk of a system being left in a partially configured state, although it doesn't eliminate it entirely, as we'll see later on. Another benefit of this approach is that Nixos stores previously applied configurations as generations which are able to then be rolled back to in the event of an issue.
05:00 Whilst being able to roll back to a previous generation is incredibly useful, I find it doesn't happen that often due to the fact that my configuration is version controlled using Git, which allows me to track and review all of the changes made to it, whether it's by my own hand or through an AI agent. All of this results in what I think is the biggest benefit when it comes to using Nixos, which is that my configuration effectively becomes the single source of truth for my entire system.
05:26 And I don't mean just a single VPS instance. As in my case, I use it to manage my entire fleet. This is because Nyxos is incredibly reproducible in a way that still supports customization, which means I can take my existing configuration and reuse it to rebuild the exact same system onto another Nixos machine. Whether it's a brand new VPS instance, a bare metal server, or even a virtual machine that I can then use for local testing.
05:52 By the way, if you want to use this configuration for your own VPS instance, you can find it on GitHub alongside instructions on how to use and modify it. There's a link in the description down below. As for the actual instance that I'm using, this is the KVM2 from Hostinger, which comes with two vCPUs and 8 gigs of RAM. It also happens to be the same instance that I use for my production services.
06:14 Thanks to Hostinger's current Black Friday sale, the KVM2 is available for only $879 a month when you purchase a 24-month term. And if you use my coupon code, dreams a code when checking out, you can save an additional 10% off. So, to pick up your own Nixos VPS instance, then head on over to hostinger.com/dreams a code and make sure to use my coupon code dreams of code when checking out to save that additional 10% off.
06:36 A big thank you to Hostinger for sponsoring this video. As you can see, Nixos provides a number of features that are very valuable when it comes to infrastructure, specifically on a VPS. And as I mentioned earlier, it's also one of my favorite ways to manage Docker containers. For example, let's say I want to deploy EngineX as a Docker container. To do so, I would add in the following entry into the OCI containers configuration.
07:00 And once it's been redeployed, there'll be an instance of EngineX running on my VPS through a Docker container. Although that's not entirely accurate because if I check to see which Docker containers are running, you'll see there are in fact none. So, what is going on? Well, this is because the OCI containers module doesn't use Docker for its back end and instead it uses Podman.
07:22 Oh, you've got to be joking. Please don't say you just replace Docker with Podman. Whilst this would be a somewhat convenient answer, it's not the reason why, especially as it's possible to change the backend to be Docker if you want to. Instead, the reason why I'm no longer using Docker is because it undermines some of the main benefits of using Nixos in the first place.
07:44 For example, in my container configuration, let's say I accidentally reference an image tag that doesn't exist. Whilst this does throw an error when I try to deploy, it unfortunately doesn't occur during the build. Instead, it occurs during activation, which not only leaves my configuration in a partially configured state, but it causes my deployed application to no longer be running.
08:04 Not good. This happens because the image pool takes place on the VPS itself after the configuration has been activated, which means it's not checked when the configuration is being built. Now, it's fair to say that automation can reduce the risk of accidentally referencing an image that doesn't exist. However, because I'm pushing my images to a container registry, which is effectively third party infrastructure, then there's still a risk that an image might not be accessible.
08:29 whether it's an expired authentication token, a simple networking issue, or more likely in 2026, a service outage. There are, of course, other approaches when it comes to distributing images that don't rely on a registry, such as transferring a local image or building it on the box itself. However, both of these approaches undermine another benefit of using Nyx OS, reproducibility.
08:49 For example, if I try to redeploy this configuration onto a brand new instance because the image isn't on the machine already, then the service will fail. Through considering each of these approaches when it came to managing containers, I came to the realization that my Nixxos configuration was no longer the single source of truth. As my Docker images were effectively a form of external state that was being managed somewhere else.
09:14 Therefore, in order to restore my configuration as the single source of truth, I needed to integrate my Docker images into the configurations Nyx store. One way of doing this was through the pull image function of the Docker tools package, which is used to pull down a Docker image into the Nyx store during the configuration build, removing the need for it to be pulled down by the VPS during runtime.
09:34 This would not only prevent an inaccessible image from being applied to the VPS, but it also meant I could remove the orth configuration for the registry as the image would be pulled by my local machine. On first glance, the pull image function seemed exactly like what I was looking for. However, it still left me with the existing workflow for publishing a Docker image.
09:54 Instead, I wanted an approach that would allow me to build a Docker image alongside the rest of my configuration. As it turns out, Nyx provides a function that does just this. The build image function of the Docker tools package. Although it doesn't work exactly how you might think. For starters, it doesn't build a Docker image from a Docker file. In fact, it doesn't even use Docker at all.
10:19 Instead, it builds a Docker compatible Tarble using Nyx. This means in order to use it, I first needed to turn my applications into Nyx packages, which I like to do using Nyx flakes. This sounds kind of fancy, but all it means is adding a flake.Nix into the application repository, which tells Nyx how to build the application as a package, as well as any dependencies it requires.
10:39 In a way, it's rather similar to a Docker file. One major benefit of using Nyx Flakes is that the application repository can then be imported as a dependency by other Nix Flakes, of which my VPS configuration also happens to be. This means the package can then be used within the configuration such as with the build image function. This function isn't configured using a Docker file or Docker file syntax and instead it's defined entirely using Nyx.
11:04 Although when I compare it to the runtime stage of my Docker file, you can see it's doing pretty much the same thing. With the build image function defined and its results being used with the container configuration, my application's Docker images were now being built alongside the rest of my Nyx configuration, effectively achieving what I wanted. This not only resulted in a much simpler workflow, but it also brought a number of other improvements.
11:27 For starters, if I wanted to redeploy my application following an update, all I needed to do was update the applications commit inside of my repository using the Nyx Flake update command, which updates the associated lock file inside of my VPS configuration, and then just redeploy. Another improvement came from the fact that my configuration and application were being built on my local machine, which not only meant I no longer needed third party infrastructure such as a container registry, but thanks to the fact that
11:54 my local machine has access to my private repository over SSH, then there was no need to set up authentication on the VPS itself. Similar to when I was using the pull image function, this makes the workflow for a firsttime deployment much simpler as there's no need to worry about generating a token and ensuring it's handled correctly. Another improvement was when it came to the build process.
12:16 If this happened to fail for some reason, such as the repository no longer being accessible, then the VPS will remain unchanged rather than being left in a partially configured state. By using the build image function, I had managed to integrate my Docker images into my configuration without undermining the benefits of using Nixos, effectively restoring my configuration as the single source of truth.
12:35 This wasn't all it had accomplished, however, as the approach led me to another realization. I no longer need to use Docker because my application was now being built as a Nyx package alongside my configuration. Then I was able to define a systemd service that used this package directly. This meant I could bypass the need to build a Docker image whilst still retaining the improved workflow.
12:58 Therefore, I decided to delete the OCI containers module which not only helped to simplify my system further, but it also provided a very good solution for some of the quirks of using either Docker or Podman on a VPS. However, before I was able to commit to moving away from Docker entirely, there was still one feature provided by OCI containers that I wanted to have for my own application deployments.
13:23 This is a feature I've become very comfortable with over the years, one that I very much felt the absence of when I no longer had it. Not only does isolation prevent issues like poor collisions, file system conflicts, and process interference, but when configured correctly, it can also limit the amount of potential damage an application can cause if it ever becomes compromised.
13:44 Given the absolute state of 2026, this was a feature I definitely wanted to keep. Fortunately, Nyx provides a couple of ways to isolate services without the need for Docker. The first approach is through systemd sandboxing which isn't something that's unique to Nixos. The second approach, however, is something that's a little bit more interesting. Nixos containers.
14:07 These are miniature isolated Nixos systems, each of which can be configured with their own private networking file system, and even the option of being ephemeral. They also support other features one would typically expect from OCI containers including port forwarding volume mounts and resource limits. Because a Nixos container has its own configuration, then it's able to be configured with its own installed packages, firewall rules, and of course services.
14:36 This means by importing my existing service into my container configuration, I can run my application in an isolated environment all without the need for Docker and managed entirely using Nyx. There is of course one drawback to this approach. Boiler plates. Configuring a Nyx OS container and systemd service from scratch every time can be somewhat tedious.
14:58 Fortunately, however, there's no need to redefine this configuration from scratch every time I want to deploy a new application. Because a Unix configuration is entirely programmable, it means it's possible to create an abstraction for common configuration patterns. For example, in my own production instances, I have the following interface for defining a service where all I need to do is select the package that I want to execute and configure any properties that I want, such as defining any ports my application
15:23 exposes, the SOPs file containing my secrets, and any environment variables. In addition to these properties, I've also added some others that have been tailored towards my own setup, such as whether or not the service requires any internal or external ingresses, including its desired domain. Setting either option will cause the service to then be exposed using either Tailscale Serve when it comes to internal ingresses or Cloudflare Tunnel for external ones.
15:46 All of this results in what I would consider to be the best user experience I've ever had when it comes to deploying applications onto a VPS and is why I'm no longer using Docker in order to do so. Now, this doesn't mean I've given up using Docker entirely, and I'm still using it for a couple of reasons, such as deploying a Docker application that has been distributed as an image, or when it comes to my home lab, which is still running K3s, at least for the moment.
16:13 However, when it comes to deploying my own applications onto a VPS, because Nyxos provides pretty much everything I need, I can't see myself going back anytime soon. I want to give a big thank you to Hostinger for sponsoring this video and adding support for Nyx OS. If you want to pick up your own NixosVPS instance, then now is the perfect time thanks to Hostinger's current Black Friday sale.
16:34 Therefore, head on over to hostinger.com/dreams of code and make sure to use my coupon code dreams of code when checking out in order to save an additional 10% off. And if you want to use my configuration with your new VPS instance, then you can find a link to it in the description down below.