code-server + Remote-SSH on Synology
Building a stable dual workflow while fixing the USB SSD permission trap that keeps breaking SSH
I started this project for a boring reason: I wanted editing on my NAS to stop feeling like work.
Most days I touch two things:
- Home Assistant configuration
- Docker Compose stacks
I wanted both browser editing and desktop VS Code, pointing to the same environment.
Before this: SMB editing from my Mac
Before code-server, I mounted NAS folders over SMB on my MacBook and edited files there.
That worked for quick edits, but Git was unreliable enough to make me nervous, especially for Home Assistant and Docker Compose repos that I rely on.
The repo lives on Linux (NAS), but edits were happening remotely over SMB from macOS. That cross-filesystem setup can introduce subtle Git friction:
- file mode and ownership behavior can differ from native Linux expectations
- case-sensitivity behavior can differ (macOS defaults vs Linux)
- rename/lock/mtime behavior can be less predictable on network shares
- occasional index/lock edge cases show up at the worst time
Even when each issue is fixable, the workflow felt fragile for configuration that I needed to version reliably.
The easy part
I followed LinuxServer's code-server docs:
That got me to a working web editor quickly. At that point I thought I was basically done.
The part that failed
I wanted desktop VS Code Remote Development too, so I first tried Remote-SSH to the Synology host directly.
That failed for a practical reason: the host side did not reliably satisfy VS Code Server prerequisites.
So I changed strategy:
- keep
code-serverin Docker for browser editing - run SSH inside that same container for VS Code Remote-SSH
One container, two workflows.
The bug that kept coming back
My Docker data is on a USB-attached SSD on Synology.
Everything looked correct until container rebuilds. Then SSH would fail again with:
abc@diskstation: Permission denied (publickey)
The key was there. The client config was right. Still blocked.
The real issue was ownership and permission drift on .ssh and authorized_keys on that storage path. With StrictModes yes, OpenSSH rejects even small mismatches.
In practice, after rebuilds the .ssh owner would often stop matching the expected SSH user (abc).
When that happened, login failed until I SSHed into the NAS host and ran chown on the directory.
What finally made it stable
The final setup was not one big fix. It was a set of small, explicit constraints:
- OpenSSH inside the
code-servercontainer - key-only auth (
PasswordAuthentication no) - strict checks (
StrictModes yes) - startup init that enforces
/config/.sshpermissions - one-time post-start permission fixer for Synology/USB behavior
- helper script that updates
authorized_keysand reapplies safe perms
I also kept the SSH user as LinuxServer's default abc. Trying to rename that user causes friction with LinuxServer init/runtime expectations.
Why I published it
After repeating this troubleshooting cycle enough times, I packaged the working setup and published it so others can start from the stable version:
Project references:
How I use it now
This is now my default homelab editing path:
- quick edits in browser via
code-server - heavier work in desktop VS Code over Remote-SSH
The target content is mostly Home Assistant YAML and Docker Compose files.
Editing inside this container feels like editing directly on the server (similar to SSH workflow), so commits are created in the same Linux environment where the repo actually lives.
That is the key reliability improvement for me.
Security boundaries I keep
I do not expose this service directly to the internet.
Remote access flow is simple:
- connect to home VPN first
- then use SSH or
code-server
That keeps the setup useful without adding unnecessary exposure.
Closing thought
The LinuxServer base gets you most of the way quickly. The hard part was making SSH auth survive rebuilds on Synology USB-backed storage while keeping strict SSH checks enabled.
If your key is correct but auth still fails, check these first:
/config/.ssh/config/.ssh/authorized_keys
In my case, that was the actual root cause every time.
Cover photo by luis gomes - Close Up Photo of Programming of Codes on Pexels