Docker setup
Run CW in Docker and persist state by mounting /config.
Run CW in Docker. Always persist /config.
Expose 8787.
Use either a Docker volume or a bind mount for /config.
Both options work fine.
If you use Portainer or a NAS container UI, use these same values.
We do not provide support for installing or troubleshooting container runtimes.
These pages assume Docker (or your NAS container manager) is already installed and working.
If Docker itself is broken, use your platform docs first.
Fastest path for most users:
use a Docker volume
keep port
8787set only
TZ
Quick start
Which should I use?
Both options are perfectly fine.
Pick the one that fits your setup.
Pick a Docker volume if you want:
the simplest setup
Docker to manage storage for you
Pick a bind mount if you want:
files in a known host path
a NAS share or fixed folder layout
Quick rule:
Use a Docker volume for simpler Docker-managed storage
Use a bind mount for a specific host folder
Bind mounts need correct host permissions.
CW must be able to write to the mounted directory.
Before you run
Pick one storage type for
/config.Docker volume:
crosswatchBind mount:
/srv/crosswatch/config
Decide which port to expose.
Default UI port:
8787
Set your timezone.
Example:
TZ=Europe/Amsterdam
Do not run without persistent storage on /config.
You will lose state when the container is recreated.
Health check
CW exposes GET /healthz.
Expected response:
It returns success when the app is ready.
The default examples below stay minimal.
If you want container health status, use the separate examples later on this page.
Option A: Docker volume
Create the volume once:
Run CW:
Open http://localhost:8787.
Option B: Bind mount
Create a host directory first (below is just an example!):
Check permissions before you start.
CW must be able to read and write the host directory.
By default, the container runs as UID 1000 and GID 1000.
If needed, either:
change the host directory owner to
1000:1000, orset
APP_UIDandAPP_GIDto match the host directory owner
Run CW:
Open http://localhost:8787.
Container user and permissions
CW runs as a dedicated user inside the container.
It does not run the application as root.
Default container identity:
Docker managed volume
When /config uses a Docker managed volume, permission setup is usually not required.
Keep the defaults.
You can omit all four APP_* variables.
Example:
CW creates the runtime user and group.
It prepares /config and applies the required ownership automatically.
Host directory bind mount
When /config maps to a host directory, the CW user must be able to read and write that directory.
Set APP_UID and APP_GID to match the host directory owner.
Leave APP_USER and APP_GROUP as appuser unless you need different names inside the container.
Example:
In this example, CW runs inside the container as UID 1026 and GID 100.
Find the correct UID and GID
Use one of these commands:
The first number is the UID.
The second number is the GID.
Example output:
Use those values here:
Quick rules
Docker managed volume:
keep the defaults
omit
APP_*unless you have a specific reason
Host directory bind mount:
set
APP_UIDandAPP_GIDto match the host directory ownerleave
APP_USERandAPP_GROUPasappuser
APP_USER and APP_GROUP are container-only names.
They do not need to match names on the Docker host.
Permission problems
When CW reports permission errors for /config, check the host directory ownership:
Then set APP_UID and APP_GID to the numeric owner and group shown there.
Or change the host directory ownership to match your configured values:
Use the UID and GID that fit your environment.
Do not set the Docker Compose user property together with these variables.
CW needs startup permissions to prepare /config.
After that, it starts the application as the configured runtime user.
Optional: Docker health check
Use this if you want Docker to mark the container as healthy or unhealthy.
It polls http://127.0.0.1:8787/healthz.
Defaults used below:
interval:
30stimeout:
5sretries:
3start period:
20s
Docker volume + health check
Bind mount + health check
Optional environment variables
You normally only need TZ.
Override these only if you know why:
TZ— container timezone (used for timestamps in logs and the UI).CONFIG_BASE— base path forconfig.json,state.json,statistics.json, etc.Default:
/config(leave as-is)
WEB_HOST— bind address for the web UI inside the container.Default:
0.0.0.0
WEB_PORT— port for the web UI inside the container.Default:
8787
APP_UID— numeric UID for the runtime user inside the container.Default:
1000
APP_GID— numeric GID for the runtime group inside the container.Default:
1000
APP_USER— runtime username inside the container.Default:
appuser
APP_GROUP— runtime group name inside the container.Default:
appuser
APP_DIR— application directory inside the container.Default:
/app(leave as-is)
RUNTIME_DIR— runtime/config directory used by the entrypoint.Default:
/config(leave as-is)
CW_RESET_AUTH_ONCE— one-time UI auth recovery flag.Set to
1to clear stored auth and sessions on next start.Remove it after the reset completes.
RELOAD— enable Python auto-reload for development.Default:
no(set toyesfor dev only)
Use an IANA timezone name (like Europe/Amsterdam), not a GMT offset.
Troubleshooting
UI does not load
Confirm the container is running:
docker psCheck logs:
docker logs crosswatch --tail 200Confirm the port mapping matches the URL you open.
Example:
-p 8787:8787->http://localhost:8787
/config is not writable
/config is not writableThis happens most often with bind mounts.
Check the host directory exists.
Check the container user can write to it.
Align the host directory owner with
APP_UIDandAPP_GID.On NAS platforms, also verify the shared folder ACLs.
Next steps
First-time setup - connect providers and create your first pair
Best practices - safer defaults for your first pairs
Settings - connect providers and configure pairs
Last updated
Was this helpful?