Omicscloud Docker配置

来自OmicsWiki
Liuhebin讨论 | 贡献2026年5月22日 (五) 08:22的版本 (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.