Docker Troubleshooting Case Study: Deploying Zusam on YunoHost
The goal was to deploy Zusam (a private social network) using Docker Compose on a VPS running YunoHost. The setup involved a Docker container exposing a specific port (18940) and a YunoHost "Custom Webapp" acting as a reverse proxy to make the service accessible via a public domain.
2. Issue #1: The Reboot Loop (Permission Denied)
Symptoms
Upon starting the container, the logs showed a successful database initialisation, but the container immediately entered a restart loop. The logs ended with chown and crontab commands, followed by a crash.
zusam-1 | + chown -R 1000:1000 /zusam /etc/s6.d ...
zusam-1 | + su-exec 1000:1000 /bin/s6-svscan /etc/s6.d
# Container restarts
Root Cause Analysis
This is a classic UID/GID Mismatch.
- The Host: The directory
~/docker/zusam/datawas owned by the host user (kalvin0x58c, UID:23691). - The Container: The Zusam Docker image is hardcoded to switch to internal user
1000(abcorwww-data) to run the application. - The Conflict: When the container (UID 1000) tried to write to the mapped volume (owned by UID 23691), the Linux kernel denied permission.
Failed Attempt: Force User in YAML
We attempted to force the container to run as the host user by adding the user: directive to docker-compose.yml:
# ❌ INCORRECT APPROACH FOR THIS IMAGE
user: "23691:23691"
Result: The container failed even earlier with crontab: must be suid to work properly.
Reason: The Zusam container acts as an init script. It must start as root to perform setup tasks (like fixing permissions and setting up cron) before it voluntarily drops privileges to user 1000. Forcing it to start as a non-root user broke this initialization chain.
✅ The Solution: Correct Host Permissions
Instead of changing the container's behavior, we aligned the host storage with the container's expectation.
- Remove
user:directive fromdocker-compose.ymlto let the container start as root. - Change ownership of the host data directory to match the container's internal UID (1000).
# Run on host machine
sudo chown -R 1000:1000 ~/docker/zusam/data
- Rebuild and restart:
docker compose up -d --build --force-recreate --renew-anon-volumes
3. Issue #2: Reverse Proxy "Welcome to Nginx" Error
Symptoms
The container was running successfully (verified by docker ps and curl localhost:18940), but accessing the domain (zusam.nulu.my) displayed the default "Welcome to nginx!" page instead of the application.
Root Cause Analysis
The YunoHost Reverse Proxy (configured via the "Custom Webapp" tool) was not correctly forwarding traffic to the Docker container.
Potential misconfiguration candidates:
- Public IP Loopback: Pointing the proxy to the VPS public IP (
46.x.x.x:18940) often fails due to Hairpin NAT or firewall blocking. - Missing
proxy_pass: If the configuration is incomplete, Nginx defaults to serving its static welcome page.
✅ The Solution: Localhost Proxying
When the Docker container and the Nginx proxy reside on the same physical server, the proxy destination must be the localhost loopback interface. This bypasses the firewall and ensures internal routing.
Configuration for YunoHost Custom Webapp:
- Domain:
zusam.nulu.my - Path:
/ - Port / URL:
http://127.0.0.1:18940(Do not use the public IP)
Verification
If the "Welcome to Nginx" page persists, the configuration file for the specific domain must be checked manually for the proxy_pass directive:
# /etc/nginx/conf.d/zusam.nulu.my.conf
location / {
proxy_pass http://127.0.0.1:18940;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
# ...
}
4. Summary of Lessons
- Respect Container Init Scripts: Some containers require root at startup. Do not use the
user:directive in Docker Compose unless the documentation explicitly supports it. - IDs are Numbers: Linux file permissions care about UIDs, not usernames.
chown 1000:1000is often necessary for persistent volumes, even if that ID doesn't exist on the host system. - Proxy Locally: Always use
127.0.0.1orlocalhostwhen reverse proxying to a Docker container on the same server to avoid firewall and NAT issues. - Rebuild Cleanly: When in doubt, use
docker compose up -d --build --force-recreateto ensure no stale configuration or cached layers remain.
No comments to display
No comments to display