Dev Blog

Remote Development Setup: Code-Server, Coolify & Hetzner - and the Nastiest Architecture Traps

As a freelance software developer and IT consultant, flexibility is everything. Whether I am working on complex Java Spring Boot backends, building JavaScript frontends, or rendering Three.js visualizations, my development environment needs to be fast, reproducible, and accessible from anywhere.

That is why I left the traditional hosting approach behind and moved my infrastructure to a dedicated Hetzner server, fully orchestrated by Coolify. At its heart is a remote instance of VS Code (Code-Server).

In theory, this is a dream setup. In practice, I ran into architectural traps that nearly drove me mad. Here are the biggest pitfalls and how to avoid them cleanly.

Trap 1: The port proxy illusion (or: the black screen of death)

VS Code in the browser offers a seemingly brilliant feature: integrated port forwarding. When you start a local test server, for example on port 4096, the IDE automatically tunnels it to a URL such as https://your-ide.com/proxy/4096/.

For pure backend development, such as a Spring Boot REST API that only returns JSON, this is fantastic. No firewall rules and immediate access.

However, as soon as you launch a modern frontend with Vue, React, or Vite, or any tool with a graphical interface, you may be greeted by a black screen and endless 404 errors in the console.

The reason: Modern frameworks compile with absolute paths, for example <script src="/main.js">. The browser completely ignores the /proxy/4096/ subdirectory, looks for the files at the server root, and fails.

The solution: Traefik and subdomains

The only clean architecture is to ignore the integrated VS Code tunnel completely. Instead, use Coolify’s Traefik reverse proxy:

  1. Create a wildcard DNS record (*.yourdomain.com) at your domain provider and point it to your Hetzner server.
  2. In Coolify, add a new subdomain to the container’s domains, including the internal port: https://dev.yourdomain.com:4096.
  3. Traefik automatically handles the SSL certificate and routes the clean public URL, without a port suffix, directly into the container. The frontend now loads correctly.

Trap 2: The localhost misconception (502 Bad Gateway)

You have configured the subdomain in Coolify and look forward to the live preview, only to receive an ice-cold 502 Bad Gateway. Traefik is running, but the container refuses the connection.

This happens when your internal server, such as Five Server or your own tool, listens on 127.0.0.1 by default. In the Docker world, that means the service communicates only with itself inside the container. When the Traefik proxy knocks from outside, the door remains closed.

The fix: Force your test server to listen on all network interfaces. If you start it from the terminal, use --hostname=0.0.0.0 or set the environment variable HOST="0.0.0.0". If you use a VS Code extension such as Five Server, update settings.json:

{
  "fiveServer.host": "0.0.0.0",
  "fiveServer.port": 5500
}

Trap 3: Docker amnesia (“Restart” today, gone tomorrow)

You carefully configure your .bashrc with useful aliases, generate an SSH key for GitHub, stop the container through Coolify, and click “Deploy” the next day. Suddenly everything is gone. Your aliases have disappeared and Git asks for passwords again.

This is not a bug; it is a feature. A stopped container briefly appears with a “Restart” option. Overnight, Coolify’s garbage collection cleans up the server and completely removes the dormant container. Clicking “Deploy” builds a fresh container from the base image, so anything that was not persistent is lost.

The clean structure

Never rely on ephemeral container storage. Use bind mounts or Docker volumes for everything that needs to survive.

volumes:
  - type: bind
    source: /opt/appdata/workspace # Your code
    target: /workspace
  - type: bind
    source: /opt/appdata/config    # VS Code settings, SSH keys & .bashrc
    target: /config

Trap 4: CORS configuration behind the reverse proxy

The final trap often appears during full-stack development. Your frontend runs at https://dev.yourdomain.com and sends requests to your Spring backend. Out of habit, you include the port in your CorsConfiguration:

// WRONG when a reverse proxy sits in front!
configuration.setAllowedOriginPatterns(
    Arrays.asList("https://dev.yourdomain.com:5500")
);

The backend blocks the request again. Why? Port 5500 is only an internal routing instruction in Coolify. Externally, your browser communicates transparently with the Traefik proxy over the standard HTTPS port 443. Therefore, the browser sets the Origin header to simply https://dev.yourdomain.com.

Remove the port from the backend configuration and the CORS errors disappear.


Conclusion: Running your own Coolify and Code-Server setup on Hetzner initially requires some patience and a solid understanding of Docker. Once the routing is clean, the volumes are mounted, and the ports are correct, however, you have a high-performance private cloud IDE that is every bit as capable as traditional SaaS solutions.