Omicscloud Docker配置
# OmicsCloud full application deployment
This deployment layout packages the full OmicsCloud application into one image and keeps the mutable parts outside the image:
- `conf`
- key files
- MariaDB
- LDAP
- mounted runtime data under the legacy in-container paths
The Linux username on the target VM does not need to be `saitoasuka`. The container keeps `/home/saitoasuka/...` only because parts of the OmicsCloud codebase still use those fixed paths internally.
## Table of Contents
- [Choose a Deployment Path](#choose-a-deployment-path)
- [Repository File Groups](#repository-file-groups)
- [Quick start](#quick-start)
- [Create An Offline Package](#create-an-offline-package)
- [Deploy From An Offline Package](#deploy-from-an-offline-package)
- [Ubuntu VM First-Time Setup](#ubuntu-vm-first-time-setup)
- [Connection modes for MariaDB and LDAP](#connection-modes-for-mariadb-and-ldap)
- [Host-network restore mode](#host-network-restore-mode)
- [Using the bundled infrastructure profile](#using-the-bundled-infrastructure-profile)
- [GitHub updates](#github-updates)
- [Fresh VM GitHub Test Run](#fresh-vm-github-test-run)
- [Notes](#notes)
- [Runtime logs](#runtime-logs)
- [Troubleshooting checklist](#troubleshooting-checklist)
- [Common Problems](#common-problems)
- [`localhost` Cannot Connect To MariaDB Or LDAP](#localhost-cannot-connect-to-mariadb-or-ldap)
- [`/index/` Or `/dashboard/` Returns 500](#index-or-dashboard-returns-500)
- [Dashboard Redirects Back To Index](#dashboard-redirects-back-to-index)
- [MariaDB Works On The VM But Not In The Container](#mariadb-works-on-the-vm-but-not-in-the-container)
- [UTF-8 Or Chinese Text Behaves Differently](#utf-8-or-chinese-text-behaves-differently)
- [Existing `omicscloud-net` Warning](#existing-omicscloud-net-warning)
- [`address already in use` On Port `3838`](#address-already-in-use-on-port-3838)
- [`Permission denied` Under Project Data](#permission-denied-under-project-data)
- [Build Is Slow](#build-is-slow)
- [Container Restarts With Read-Only `chown` Errors](#container-restarts-with-read-only-chown-errors)
## Choose a Deployment Path
Use this table first. It decides which section and command set applies.
| Situation | Use | Main commands |
| --- | --- | --- |
| Fresh VM with internet access and a GitHub repo | Online build from source | `docker compose build omicscloud-app` then `docker compose up -d omicscloud-app` |
| Current VM already has a known-good running container and you need a transferable package | Create offline package | `docker commit`, `docker save`, `tar` |
| New VM has no internet or should not rebuild R packages | Deploy offline package | `docker load`, extract compose/source, run restore script |
| MariaDB/LDAP run as normal network services reachable from the container | Default bridge compose | `docker compose up -d --no-build omicscloud-app` |
| MariaDB is host-local only, such as `127.0.0.1:1430` | Host-network restore | `bash deploy-restore-hostnet.sh` |
| You want bundled placeholder MariaDB/OpenLDAP containers | Infra profile scaffold | `docker compose --profile infra up -d` |
Key distinction:
- `docker-compose.yml` is the default bridge-network deployment.
- `docker-compose.hostnet.yml` is for legacy VMs where the app must share the host network to reach host-local MariaDB/LDAP.
- `deploy-restore-hostnet.sh` is a convenience script for offline restore on those legacy VMs; it also handles port conflict and writable data-directory setup.
## Repository File Groups
Docker build and runtime files:
- `Dockerfile.app`: full OmicsCloud image
- `Dockerfile.app.offline`: rebuilds the app layer from an existing local base image
- `docker/shiny-server.conf`: serves the whole repository through `shiny-server`
- `docker/conf/conf.example`: template config for Docker deployment
- `docker-compose.yml`: app service plus optional MariaDB and OpenLDAP services
- `.env.example`: template for host paths, image tag, ports, and optional bundled infra values
Host-network/offline restore files:
- `docker-compose.hostnet.yml`: app service using host networking for legacy host-only MariaDB/LDAP
- `deploy-restore-hostnet.sh`: restore/deploy helper for host-network offline deployments
Online update helpers:
- `deploy.sh` / `deploy.ps1`: pull GitHub updates, rebuild, restart
Generated offline package files are not committed to the repository. They are created under a dated directory such as:
```text
~/Downloads/omicscloud-offline-20260521/
omicscloud-images-20260521.tar.gz
omicscloud-compose-20260521.tar.gz
RESTORE.txt
```
The generated package contains the Docker images and deployment source/config files. It does not contain the large external runtime resources:
```text
/home/saitoasuka/mnt/omicscloud/data
/home/saitoasuka/mnt/share
/home/saitoasuka/mnt/database
```
## Quick start
```bash
docker compose build omicscloud-app
docker compose up -d omicscloud-app
```
On Ubuntu servers where your user is not in the `docker` group, prefix Docker commands with `sudo`.
Then visit:
- `http://localhost:${SHINY_PORT:-3839}/`
- `http://localhost:${SHINY_PORT:-3839}/index/`
- `http://localhost:${SHINY_PORT:-3839}/dashboard/`
Important:
- `docker compose up -d omicscloud-app` only starts the current image. It does not rebuild after source changes.
- After changing any application file copied into the image, `Dockerfile.app`, or `docker/start-omicscloud.sh`, run `docker compose build omicscloud-app` again.
- The running container does not live-sync repository files from the host. The repository is copied into the image at build time.
- The background analysis queue is started by `docker/start-omicscloud.sh` and writes to `/var/log/shiny-server/omicscloud-worker.log`.
## Create An Offline Package
Use this when the current VM has a working OmicsCloud Docker image and you want to transfer it to another VM without rebuilding from the internet.
First, make sure the currently running container has the tested packages and code fixes. Then commit it into the local image tags and write a dated bundle:
```bash
cd ~/Downloads/omicscloud
stamp=20260521
bundle=~/Downloads/omicscloud-offline-$stamp
mkdir -p "$bundle"
# Freeze the current known-good running container into local image tags.
sudo docker commit omicscloud-app omicscloud-app:local
sudo docker tag omicscloud-app:local omicscloud-app-base:$stamp
# Export the images for offline transfer.
sudo docker save omicscloud-app:local omicscloud-app-base:$stamp \
| gzip -c > "$bundle/omicscloud-images-$stamp.tar.gz"
# Archive the compose/source/config tree.
tar \
--exclude='.git' \
--exclude='*.tar.gz' \
-czf "$bundle/omicscloud-compose-$stamp.tar.gz" \
-C ~/Downloads omicscloud
cat > "$bundle/RESTORE.txt" <<EOF
Restore OmicsCloud offline package
1. Load image:
gunzip -c omicscloud-images-$stamp.tar.gz | sudo docker load
2. Extract compose/source:
mkdir -p ~/Downloads/omicscloud-restore-test
tar -xzf omicscloud-compose-$stamp.tar.gz -C ~/Downloads/omicscloud-restore-test --strip-components=1
3. Start app:
cd ~/Downloads/omicscloud-restore-test
bash deploy-restore-hostnet.sh
4. Verify:
sudo docker compose -f docker-compose.hostnet.yml ps
sudo docker exec -it omicscloud-app bash -lc 'Rscript -e "library(reactwidgets); cat(\"reactwidgets ok\\n\")"'
EOF
ls -lh "$bundle"
```
Transfer the whole bundle directory:
```text
~/Downloads/omicscloud-offline-20260521
```
If you are testing the package on the same VM, extract it to a different directory such as `~/Downloads/omicscloud-restore-test` and start from there. The container name is still `omicscloud-app`, so stop any existing app container first.
Before transferring, check the package contents:
```bash
ls -lh "$bundle"
tar -tzf "$bundle/omicscloud-compose-$stamp.tar.gz" | head
gunzip -t "$bundle/omicscloud-images-$stamp.tar.gz"
```
Expected contents:
```text
omicscloud-images-<date>.tar.gz Docker images, loaded with docker load
omicscloud-compose-<date>.tar.gz compose/source/config tree
RESTORE.txt short restore notes
```
The package is only complete if the source archive contains the host-network restore files:
```text
omicscloud/docker-compose.hostnet.yml
omicscloud/deploy-restore-hostnet.sh
```
## Deploy From An Offline Package
On a new VM, load the image archive before running Compose. `--no-build` requires the image to already exist locally.
```bash
cd ~/Downloads/omicscloud-offline-20260521
gunzip -c omicscloud-images-20260521.tar.gz | sudo docker load
mkdir -p ~/Downloads/omicscloud-restore-test
tar -xzf omicscloud-compose-20260521.tar.gz \
-C ~/Downloads/omicscloud-restore-test \
--strip-components=1
```
For VMs where MariaDB/LDAP are host-local services, run the host-network restore script:
```bash
cd ~/Downloads/omicscloud-restore-test
bash deploy-restore-hostnet.sh
```
The script handles the most common restore-time differences:
- stops a host `shiny-server` that already owns port `3838`;
- rewrites `docker/conf/conf` for the current host IP;
- defaults to `db_host=127.0.0.1`, `db_port=1430`, and `ldap://127.0.0.1:389`;
- creates external data/share/database directories if missing;
- grants project data and share write permissions to the container `shiny` user (`999:999`);
- starts the app through `docker-compose.hostnet.yml`;
- verifies MariaDB, LDAP, HTTP, and write access.
Use environment overrides when the new VM differs:
```bash
# MariaDB is host-local on the default port.
DB_PORT=3306 bash deploy-restore-hostnet.sh
# MariaDB is remote and LDAP is local.
DB_HOST=192.168.2.50 DB_PORT=3306 bash deploy-restore-hostnet.sh
# MariaDB and LDAP are both remote.
DB_HOST=192.168.2.50 DB_PORT=3306 LDAP_URL=ldap://192.168.2.50:389 bash deploy-restore-hostnet.sh
# Automatic IP detection picked the wrong public app IP.
HOST_IP=192.168.2.112 bash deploy-restore-hostnet.sh
```
Open the deployed app at:
```text
http://<HOST_IP>:3838/index/
http://<HOST_IP>:3838/dashboard/
```
The external data and database directories are not part of the offline package. Copy or mount the real resources separately:
```text
/home/saitoasuka/mnt/omicscloud/data
/home/saitoasuka/mnt/share
/home/saitoasuka/mnt/database
```
Minimum post-restore checks:
```bash
sudo docker image ls | grep omicscloud
sudo docker compose -f docker-compose.hostnet.yml ps
sudo docker exec -it omicscloud-app bash -lc 'Rscript -e "library(reactwidgets); cat(\"reactwidgets ok\n\")"'
sudo docker exec -it omicscloud-app bash -lc 'ldapsearch -x -H ldap://127.0.0.1:389 -s base -b "" namingContexts'
sudo docker exec -it omicscloud-app bash -lc 'curl -fsS -I http://127.0.0.1:3838/index/ >/dev/null && echo http-ok'
```
## Ubuntu VM First-Time Setup
These steps assume a fresh Ubuntu VM or an existing OmicsCloud VM where Docker will run beside the legacy services.
Install prerequisites:
```bash
sudo apt-get update
sudo apt-get install -y ca-certificates curl git openssl
```
Install Docker using the official Docker Engine packages, then verify:
```bash
docker compose version
```
On servers where your user is not in the `docker` group, use `sudo docker ...` for all Docker commands.
Clone or update the repository:
```bash
mkdir -p /opt/omicscloud
cd /opt/omicscloud
git clone <repo-url> omicscloud
cd omicscloud
```
Prepare the Docker environment files:
```bash
cp .env.example .env
cp docker/conf/conf.example docker/conf/conf
```
Prepare key files. If you already have production keys from the original deployment, place them here:
```text
etc/config/key.pem
etc/config/pubkey.pem
```
For a local-only test, generate temporary keys:
```bash
mkdir -p etc/config
openssl genrsa -out etc/config/key.pem 2048
openssl rsa -in etc/config/key.pem -pubout -out etc/config/pubkey.pem
```
Prepare external mutable directories:
```bash
sudo mkdir -p /data/omicscloud/user-data
sudo mkdir -p /data/omicscloud/share
sudo mkdir -p /data/omicscloud/database
```
They map into the container as:
```text
/data/omicscloud/user-data -> /home/saitoasuka/mnt/omicscloud/data
/data/omicscloud/share -> /home/saitoasuka/mnt/share
/data/omicscloud/database -> /home/saitoasuka/mnt/database
```
Make them writable by the container `shiny` user. Confirm the UID/GID first if the image has changed:
```bash
docker run --rm omicscloud-app id shiny 2>/dev/null || true
sudo chown -R 999:999 /data/omicscloud/user-data /data/omicscloud/share /data/omicscloud/database
sudo chmod -R u+rwX,g+rwX /data/omicscloud/user-data /data/omicscloud/share /data/omicscloud/database
```
Configure `.env`:
```dotenv
SHINY_PORT=3839
OMICSCLOUD_USER_DATA_DIR=/data/omicscloud/user-data
OMICSCLOUD_SHARE_DIR=/data/omicscloud/share
OMICSCLOUD_DATABASE_DIR=/data/omicscloud/database
```
If the original VM Shiny Server already owns `3838`, keep `SHINY_PORT=3839`.
On the current legacy VM, the real database resources may live under a mounted CIFS path:
```dotenv
OMICSCLOUD_DATABASE_DIR=/home/saitoasuka/mnt/os2/database
```
Configure `docker/conf/conf` so the app can reach MariaDB, LDAP, and external services from inside the container. Common `db_host` and `ldap_server_url` choices:
```text
Same compose project: mariadb / ldap://openldap:389
Same Docker network: other container name / ldap://container-name:389
Ubuntu host service: host.docker.internal / ldap://host.docker.internal:389
Another server or VM: real IP or DNS name
```
If MariaDB runs on the Ubuntu VM host, it must listen on a non-loopback address and the database user must allow remote container connections, not only `user@localhost`.
Build and start:
```bash
docker compose build omicscloud-app
docker compose up -d omicscloud-app
```
If you intentionally want the placeholder MariaDB/OpenLDAP containers:
```bash
docker compose --profile infra up -d
```
These placeholders do not contain the real OmicsCloud schemas or LDAP entries.
Recommended production layout:
```text
/opt/omicscloud/omicscloud
/opt/omicscloud/omicscloud/.env
/opt/omicscloud/omicscloud/docker/conf/conf
/opt/omicscloud/omicscloud/etc/config/key.pem
/opt/omicscloud/omicscloud/etc/config/pubkey.pem
/data/omicscloud/user-data
/data/omicscloud/share
/data/omicscloud/database
```
Minimal startup checklist:
1. `docker compose version` works.
2. `.env` points to real external directories.
3. `docker/conf/conf` has correct DB and LDAP targets.
4. `etc/config/key.pem` and `etc/config/pubkey.pem` exist.
5. mounted data directories exist and are writable by the container `shiny` user.
6. target MariaDB and LDAP are reachable from the container.
7. `docker compose build omicscloud-app` completes.
8. `docker compose up -d omicscloud-app` starts the service.
## Connection modes for MariaDB and LDAP
You can connect OmicsCloud to MariaDB and LDAP in three common ways:
1. Same `docker compose`
- Use service names such as `mariadb` and `openldap`.
2. Other Docker containers on the same Docker network
- Put them on `omicscloud-net` and use their container or service names.
3. Services running on the Ubuntu VM host
- Use the host IP or `host.docker.internal`.
- `docker-compose.yml` already maps `host.docker.internal` for Linux Docker.
### Host-network restore mode
Use `docker-compose.hostnet.yml` when the target VM already has legacy services that are only reachable from the host loopback interface, for example:
```text
MariaDB: 127.0.0.1:1430
LDAP: 127.0.0.1:389
Shiny: host port 3838
```
In this mode the container shares the host network namespace. This lets OmicsCloud connect to host-local MariaDB/LDAP, but it also means the host's own `shiny-server` must not already occupy port `3838`.
For an offline restore on this kind of VM, run:
```bash
cd ~/Downloads/omicscloud-restore-test
bash deploy-restore-hostnet.sh
```
The script:
- stops the host `shiny-server` service if present, to free port `3838`;
- updates `docker/conf/conf` with the current host IP, `db_host=127.0.0.1`, `db_port=1430`, and `ldap://127.0.0.1:389`;
- creates the external data/share/database directories if missing;
- grants write permissions on user data and share directories to the container `shiny` user (`999:999`);
- starts the app with `docker-compose.hostnet.yml`;
- verifies MariaDB, LDAP, Shiny HTTP, and a project data write test.
If the VM uses different host service ports, override them:
```bash
DB_PORT=3306 LDAP_URL=ldap://127.0.0.1:389 HOST_IP=192.168.2.112 bash deploy-restore-hostnet.sh
```
Common override examples:
```bash
# Host MariaDB uses the default port instead of the legacy 1430 port.
DB_PORT=3306 bash deploy-restore-hostnet.sh
# MariaDB is on another server, while LDAP stays on the current VM.
DB_HOST=192.168.2.50 DB_PORT=3306 bash deploy-restore-hostnet.sh
# Both MariaDB and LDAP are on another server.
DB_HOST=192.168.2.50 DB_PORT=3306 LDAP_URL=ldap://192.168.2.50:389 bash deploy-restore-hostnet.sh
# Force the public URL IP when automatic IP detection picks the wrong address.
HOST_IP=192.168.2.112 bash deploy-restore-hostnet.sh
```
Supported environment overrides:
```text
HOST_IP public app URL IP used in server_href
SHINY_PORT public Shiny port, default 3838 in host-network mode
DB_HOST MariaDB host written to docker/conf/conf, default 127.0.0.1
DB_PORT MariaDB port written to docker/conf/conf, default 1430
LDAP_URL LDAP URL written to docker/conf/conf, default ldap://127.0.0.1:389
OMICSCLOUD_USER_DATA_DIR host user/project data directory
OMICSCLOUD_SHARE_DIR host share directory
OMICSCLOUD_DATABASE_DIR host GO/KEGG/STRING database directory
SHINY_UID container shiny user uid, default 999
SHINY_GID container shiny user gid, default 999
```
When MariaDB is remote rather than host-local, make sure the database user allows connections from the app host or Docker host network source address. For example, a user limited to `omicscloud@localhost` will not work from another machine.
## Using the bundled infrastructure profile
The compose file includes optional placeholder services:
```bash
docker compose --profile infra up -d
```
These create empty MariaDB and OpenLDAP containers. They are only scaffolding; OmicsCloud still needs your real schemas, data, and LDAP entries.
## GitHub updates
Linux:
```bash
./deploy.sh main
```
Windows PowerShell:
```powershell
.\deploy.ps1 -Branch main
```
These scripts do:
1. `git fetch`
2. `git checkout`
3. `git pull --ff-only`
4. `docker compose build omicscloud-app`
5. `docker compose up -d omicscloud-app`
## Fresh VM GitHub Test Run
For a clean VM validation, start from a fresh clone instead of copying a hotfixed container:
```bash
sudo apt-get update
sudo apt-get install -y ca-certificates curl git openssl
mkdir -p /opt/omicscloud
cd /opt/omicscloud
git clone <repo-url> omicscloud
cd omicscloud
cp .env.example .env
cp docker/conf/conf.example docker/conf/conf
mkdir -p etc/config
openssl genrsa -out etc/config/key.pem 2048
openssl rsa -in etc/config/key.pem -pubout -out etc/config/pubkey.pem
```
Point `.env` at real writable host directories and point `docker/conf/conf` at the real MariaDB, LDAP, and legacy database resources. Then build and start:
```bash
docker compose build omicscloud-app
docker compose up -d omicscloud-app
docker compose ps
docker logs omicscloud-app --tail 120
```
Before submitting a large project, verify the runtime features that failed during Docker bring-up:
```bash
docker exec -it omicscloud-app bash -lc 'Rscript -e "library(pdftools); library(rgl); library(htmlwidgets); cat(\"report/rgl packages ok\n\")"'
docker exec -it omicscloud-app bash -lc 'Rscript -e "library(reactwidgets); cat(\"reactwidgets ok\n\")"'
docker exec -it omicscloud-app bash -lc 'ls -lh /home/saitoasuka/mnt/share/script/custom_exp.r'
docker exec -it omicscloud-app bash -lc 'grep -n "rgl::view3d\|rglwidget\|intermediates_dir" /home/saitoasuka/shiny-server/omicscloud/etc/dep/proteome_quant_lib.r /home/saitoasuka/shiny-server/omicscloud/etc/dep/proteome_script.r'
docker exec -it omicscloud-app bash -lc 'grep -n "kog_enrich_csv\|subcellular_enrich_csv" /home/saitoasuka/shiny-server/omicscloud/etc/dep/proteome_script.r'
docker exec -it omicscloud-app bash -lc 'grep -n "skip_chord_plots\|Skip GO chord plot\|Skip KEGG chord plot\|keggPngPath\|Skip KEGG pathway plot" /home/saitoasuka/shiny-server/omicscloud/etc/dep/proteome_quant_lib.r'
docker exec -it omicscloud-app bash -lc 'tail -n 120 /var/log/shiny-server/omicscloud-worker.log'
```
After a test project completes, confirm that status and outputs agree:
```bash
uid=<project_uid>
docker exec -it omicscloud-app bash -lc "
p=/home/saitoasuka/mnt/omicscloud/data/1/$uid
find \"\$p/Report/1.Report\" -maxdepth 1 -type f -name '*.html' -printf '%s %p\n'
ls -lh \"\$p/zip_file/${uid}_Report.zip\" \"\$p/para_data.xlsx\" 2>&1
"
```
## Notes
- The image keeps the application path fixed at `/home/saitoasuka/shiny-server/omicscloud` to match the hard-coded paths in the codebase.
- The image also creates a compatibility symlink from `/home/shiny/shiny-server/omicscloud` to `/home/saitoasuka/shiny-server/omicscloud` for legacy hard-coded paths.
- The root URL `/` redirects to `/index/`.
- `dashboard` requires a valid cookie plus working MariaDB and LDAP connectivity; routing alone is not enough.
- The startup script exports locale for both the container and the `shiny` runtime user. This matters because Shiny workers must be able to `source()` UTF-8 `.R` files.
- The proteome report renderer writes R Markdown intermediates under the project `Report/1.Report/.render_tmp` directory. This avoids `.knit.md` permission failures in the copied app template directory.
- The startup script still keeps the copied application tree writable by the `shiny` user for legacy code paths that write beside source files.
- The startup script also creates `/home/saitoasuka/mnt/share/script/custom_exp.r` as a compatibility symlink to `etc/dep/custom_exp.r` when the mounted share does not already provide it. Some report templates still source that legacy path directly.
- 3D PCA/PLS-DA output uses `rgl::view3d()` and `htmlwidgets::saveWidget(rgl::rglwidget(...))`; old `rgl.viewpoint()` and `writeWebGL()` calls are defunct in newer `rgl`.
- To verify locale inside the app container:
```bash
docker exec -it omicscloud-app bash -lc 'locale'
docker exec -it omicscloud-app bash -lc 'su -s /bin/bash -m shiny -c "locale"'
```
- If MariaDB runs on the Ubuntu VM host, `db_host` can be `host.docker.internal`, but MariaDB must listen on a non-loopback address such as `0.0.0.0:3306`.
- If the database user only exists as `omicscloud@localhost`, the app container can still fail to log in even when port `3306` is reachable.
- Runtime data is mounted as three separate external host directories. The container paths stay fixed because the legacy OmicsCloud code expects them:
```text
${OMICSCLOUD_USER_DATA_DIR} -> /home/saitoasuka/mnt/omicscloud/data
${OMICSCLOUD_SHARE_DIR} -> /home/saitoasuka/mnt/share
${OMICSCLOUD_DATABASE_DIR} -> /home/saitoasuka/mnt/database
```
- For production servers, set these variables to absolute paths in `.env`, for example:
```bash
OMICSCLOUD_USER_DATA_DIR=/data/omicscloud/user-data
OMICSCLOUD_SHARE_DIR=/data/omicscloud/share
OMICSCLOUD_DATABASE_DIR=/data/omicscloud/database
```
Use absolute paths in production so the deployment does not depend on a personal home directory or the current working directory.
`OMICSCLOUD_DATABASE_DIR` must point to the real legacy database resources, not an empty scaffold. It should contain files and directories such as:
```text
find_enrichment.py
go.obo
go-basic.obo
acc_list/
KEGG/
STRING/
```
For proteome KEGG plots, the mounted database should also include species-specific XML and PNG directories:
```text
KEGG/KEGG_xml/hsa/
KEGG/KEGG_png/hsa/
```
For example:
```bash
docker exec -it omicscloud-app bash -lc 'ls -lh /home/saitoasuka/mnt/database/KEGG/KEGG_png/hsa/hsa04610.png /home/saitoasuka/mnt/database/KEGG/KEGG_xml/hsa/hsa04610.xml'
```
On the current legacy VM this may be the mounted CIFS path:
```bash
OMICSCLOUD_DATABASE_DIR=/home/saitoasuka/mnt/database
```
- With the repository-local defaults, create them on the host with:
```bash
mkdir -p docker/data/omicscloud/data
mkdir -p docker/data/share
mkdir -p docker/data/database
```
- The container writes project files as the `shiny` user. Check its UID/GID and make the mounted host directories writable:
```bash
docker exec -it omicscloud-app id shiny
sudo chown -R 999:999 docker/data/omicscloud/data docker/data/share docker/data/database
sudo chmod -R u+rwX,g+rwX docker/data/omicscloud/data docker/data/share docker/data/database
```
- To confirm the real host mount sources and test writes:
```bash
docker inspect omicscloud-app --format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}'
docker exec -it omicscloud-app bash -lc 'su -s /bin/bash --login shiny -c "mkdir -p /home/saitoasuka/mnt/omicscloud/data/1/test-write && echo ok > /home/saitoasuka/mnt/omicscloud/data/1/test-write/check.txt && cat /home/saitoasuka/mnt/omicscloud/data/1/test-write/check.txt"'
docker exec -it omicscloud-app bash -lc 'ls -lh /home/saitoasuka/mnt/database/find_enrichment.py /home/saitoasuka/mnt/database/go.obo /home/saitoasuka/mnt/database/go-basic.obo'
```
If the write test fails, fix permissions on the `Source` printed by `docker inspect`, not on the path you expected to be mounted.
- If you see `a network with name omicscloud-net exists but was not created for project ...`, it usually means another compose project already created that network. This is often harmless if you intentionally share the network.
- If host port `3838` is already used by an existing service, set `SHINY_PORT=3839` in `.env` and run `docker compose up -d --force-recreate omicscloud-app`.
## Runtime logs
For startup errors:
```bash
docker logs omicscloud-app --tail 200
```
For Shiny app errors, especially after clicking buttons in `dashboard`:
```bash
docker exec -it omicscloud-app bash -lc 'f=$(ls -t /var/log/shiny-server/dashboard-shiny-*.log 2>/dev/null | head -n 1); echo "$f"; tail -n 200 "$f"'
```
For background analysis jobs that remain in progress in `Project Status`:
```bash
docker exec -it omicscloud-app bash -lc 'tail -n 200 /var/log/shiny-server/omicscloud-worker.log'
docker exec -it omicscloud-app bash -lc 'ps -ef | grep -E "omicscloud-server|proteome_script|metabolite_script|liptpp_script" | grep -v grep || true'
```
The dashboard percentage is calculated from rows in MariaDB `omicscloud_db.project_info` for the same `uid`: completed steps have `status = 5`, active steps have `status = 2`, errors have `status = 4`, and waiting steps have `status = 7`.
Use `sudo` before `docker` on servers that require it.
Common operations:
```bash
docker compose ps
docker logs omicscloud-app --tail 200
docker compose restart omicscloud-app
docker compose down
docker compose up -d omicscloud-app
docker compose build omicscloud-app
docker compose up -d --force-recreate omicscloud-app
```
Daily update from GitHub:
```bash
git fetch origin main
git checkout main
git pull --ff-only origin main
docker compose build omicscloud-app
docker compose up -d omicscloud-app
```
## Troubleshooting checklist
When the container starts but behaves differently from the original Ubuntu `shiny-server` deployment, check these in order:
1. The image was rebuilt after your latest code or Dockerfile changes.
2. `docker/conf/conf` points to the correct MariaDB and LDAP targets.
3. MariaDB listens on a non-loopback address and the app user is not restricted to `localhost`.
4. `etc/config/key.pem` and `etc/config/pubkey.pem` are mounted correctly.
5. `/home/saitoasuka/mnt/share`, `/home/saitoasuka/mnt/omicscloud/data`, and `/home/saitoasuka/mnt/database` exist inside the container.
6. `/home/saitoasuka/mnt/database` contains `find_enrichment.py`, `go.obo`, `go-basic.obo`, and the expected GO/KEGG/STRING subdirectories.
7. The `shiny` user can write to `/home/saitoasuka/mnt/omicscloud/data`.
8. The `shiny` user sees the expected locale.
9. `/var/log/shiny-server` does not show `invalid input found on input connection`, missing package errors, or hard-coded path failures.
10. `pdftools` is installed and the legacy report source path exists:
```bash
docker exec -it omicscloud-app bash -lc 'Rscript -e "library(pdftools); cat(\"pdftools ok\n\")"'
docker exec -it omicscloud-app bash -lc 'Rscript -e "library(reactwidgets); cat(\"reactwidgets ok\n\")"'
docker exec -it omicscloud-app bash -lc 'ls -lh /home/saitoasuka/mnt/share/script/custom_exp.r'
```
If `function` and `export_report` are `5` but `Report/1.Report` is empty and `*_Report.zip` is only a few bytes, inspect the project error log for report-rendering failures. Common Docker-specific causes are:
```text
不存在叫'pdftools'这个名称的程序包
无法打开文件'proteome_quant_report_summary.knit.md': 权限不够
cannot open /home/saitoasuka/mnt/share/script/custom_exp.r
```
The current code avoids the `.knit.md` permission failure by setting `intermediates_dir` to a writable project-local directory. If an already-completed project still has an empty report directory, render reports only from the saved `para.Rdata`, `rdata.Rdata`, and `result.Rdata` instead of rerunning the full analysis.
If a project fails during `stat_graphing`, inspect the project error log for newer `rgl` compatibility failures:
```text
'rgl.viewpoint' is defunct
'writeWebGL' is defunct
```
The current code uses `rgl::view3d()` and `rgl::rglwidget()` for 3D PCA/PLS-DA HTML output. Rebuild the app image after pulling these source changes.
If a proteome project fails during `function` with only a generic parallel worker error:
```text
task 1 failed - "cannot open the connection"
```
inspect the project error log and the newest files under `Report/4.Diff_Bioinformatics`. Docker-specific causes seen during bring-up were:
- missing parent directories before eggNOG/KOG or Subcellular enrichment CSV writes;
- a missing or wrong `/home/saitoasuka/mnt/database` mount;
- missing KEGG PNG/XML files;
- `keggXmlPath` becoming `NA`, which previously produced paths such as `NA/KEGG_png/hsa/hsa04610.png`.
The current code creates parent directories before those CSV writes, wraps GO/KEGG chord plot rendering in `tryCatch(...)`, and falls back to `/home/saitoasuka/mnt/database/KEGG/KEGG_xml/<species>` when KEGG XML path input is empty or `NA`. If the live container was started before these fixes, rebuild or copy the patched files into the running container before requeueing the project.
To verify the live container has these fixes:
```bash
docker exec -it omicscloud-app bash -lc 'grep -n "kog_enrich_csv\|subcellular_enrich_csv" /home/saitoasuka/shiny-server/omicscloud/etc/dep/proteome_script.r'
docker exec -it omicscloud-app bash -lc 'grep -n "skip_chord_plots\|Skip GO chord plot\|Skip KEGG chord plot\|keggPngPath\|Skip KEGG pathway plot" /home/saitoasuka/shiny-server/omicscloud/etc/dep/proteome_quant_lib.r'
```
## Common Problems
### `localhost` Cannot Connect To MariaDB Or LDAP
Inside a container, `localhost` means the container itself. Use a compose service name, another container name on the same Docker network, `host.docker.internal`, or a real host IP/DNS name.
### `/index/` Or `/dashboard/` Returns 500
Check the newest Shiny log:
```bash
docker exec -it omicscloud-app bash -lc 'ls -t /var/log/shiny-server/* | head'
docker exec -it omicscloud-app bash -lc 'f=$(ls -t /var/log/shiny-server/index-shiny-*.log 2>/dev/null | head -n 1); echo "$f"; tail -n 200 "$f"'
docker exec -it omicscloud-app bash -lc 'f=$(ls -t /var/log/shiny-server/dashboard-shiny-*.log 2>/dev/null | head -n 1); echo "$f"; tail -n 200 "$f"'
```
### Dashboard Redirects Back To Index
The dashboard requires a valid login cookie and working MariaDB/LDAP connectivity. Confirm `dashboard` can reach both services from inside the container.
### MariaDB Works On The VM But Not In The Container
Typical causes:
- `db_host=localhost` inside the container.
- MariaDB only listens on `127.0.0.1`.
- the database user only exists as `user@localhost`.
- VM firewall rules block container bridge traffic.
### UTF-8 Or Chinese Text Behaves Differently
Confirm both container and `shiny` user locale:
```bash
docker exec -it omicscloud-app bash -lc 'locale'
docker exec -it omicscloud-app bash -lc 'su -s /bin/bash -m shiny -c "locale"'
```
The `shiny` user should not fall back to `POSIX`.
### Existing `omicscloud-net` Warning
This warning is usually harmless if another compose project intentionally created the shared network:
```text
a network with name omicscloud-net exists but was not created for project ...
```
### `address already in use` On Port `3838`
Check:
```bash
sudo ss -lntp | grep 3838
docker ps --format "table {{.Names}}\t{{.Ports}}" | grep 3838
```
Use another host port in `.env`:
```dotenv
SHINY_PORT=3839
```
Then recreate:
```bash
docker compose up -d --force-recreate omicscloud-app
```
### `Permission denied` Under Project Data
Find the real host mount source:
```bash
docker inspect omicscloud-app --format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}'
```
Fix the source directory. For example, if the source is `/data/omicscloud/user-data` and `id shiny` reports `999:999`:
```bash
sudo chown -R 999:999 /data/omicscloud/user-data
sudo chmod -R u+rwX,g+rwX /data/omicscloud/user-data
```
If `docker inspect` shows a repository-local source such as `.../docker/data/omicscloud/data`, changing `/data/omicscloud` will not help. Fix the printed source or update `.env`, then recreate the container.
### Build Is Slow
The first image build is heavy because it installs R, many R packages, Java, system libraries, and Shiny Server. Subsequent builds are faster if dependency layers are cached.
If a build fails inside `RUN Rscript /tmp/install_app_packages.R`, the next `docker compose build omicscloud-app` can reuse earlier successful Docker layers. It does not resume from the exact failed R package; the whole R package installation layer runs again because the failed layer is not committed.
### Container Restarts With Read-Only `chown` Errors
The config files are mounted read-only on purpose:
```text
etc/config/conf
etc/config/key.pem
etc/config/pubkey.pem
```
If logs show `chown: changing ownership ... Read-only file system`, rebuild from a version where `docker/start-omicscloud.sh` treats read-only config ownership failures as warnings instead of startup failures.