[{"data":1,"prerenderedAt":1473},["ShallowReactive",2],{"\u002Fprojects\u002Fmulti-user-docker-lab-management":3,"$Xgs0RoTCP8":1468},{"id":4,"title":5,"body":6,"date":1456,"description":1457,"extension":1458,"image":1459,"meta":1460,"navigation":1461,"path":1462,"repository":1463,"seo":1465,"stem":1466,"tags":1459,"__hash__":1467},"projects\u002Fprojects\u002Fmulti-user-docker-lab-management.md","Multi-User Docker-based Server Management",{"type":7,"value":8,"toc":1436},"minimark",[9,18,26,59,67,73,98,104,107,128,133,139,146,158,161,186,192,195,217,223,229,266,272,279,310,316,319,368,372,377,384,386,725,730,878,883,888,899,1368,1374,1416,1420,1423,1429,1432],[10,11,14],"card",{"icon":12,"title":13},"i-lucide-info","Project Overview",[15,16,17],"p",{},"This project provisions isolated, containerized Linux environments (Ubuntu 20.04) for multiple users on a shared host server (CentOS 7). It dynamically maps host users to container users using UID\u002FGID matching, ensuring smooth read\u002Fwrite permissions for shared volumes. Each user gets their own dedicated SSH access, a pre-configured Miniconda environment, and a mapped workspace.",[19,20,22],"h2",{"id":21},"key-features",[23,24,25],"strong",{},"Key Features",[27,28,29,36,42,53],"ul",{},[30,31,32,35],"li",{},[23,33,34],{},"Dynamic User Mapping:"," Bypasses Docker's root-ownership problem. The container mimics the host system's user ID and group ID on startup.",[30,37,38,41],{},[23,39,40],{},"Isolated Environments:"," Users access their specific containers via unique SSH ports.",[30,43,44,47,48,52],{},[23,45,46],{},"Persistent Storage:"," The host user's home directory is mapped to ",[49,50,51],"code",{},"\u002Fworkspace"," inside the container.",[30,54,55,58],{},[23,56,57],{},"Legacy Kernel Compatibility:"," Configured to build successfully on CentOS 7 hosts using unauthenticated APT mirrors (Tsinghua) to bypass outdated certificate issues.",[10,60,64],{"icon":61,"title":62,"color":63},"i-lucide-circle-question-mark","What we attempted to solve","warning",[15,65,66],{},"Given the out-dated nature of CentOS 7, we were unable to install many softwares, including conda, onto the server. We also faced issues where multiple users had to use similar softwares, but there was no user management systems in place. Our solution was to set up a docker base image that we could then mirror for each person and set up an individual docker container of a Ubuntu system for each user, to which they can directly access via SSH.",[19,68,70],{"id":69},"prerequisites",[23,71,72],{},"Prerequisites",[27,74,75,81,84,91],{},[30,76,77,80],{},[23,78,79],{},"Host OS:"," CentOS 7 (or similar Linux distribution)",[30,82,83],{},"Docker installed and running",[30,85,86,87,90],{},"Root (",[49,88,89],{},"sudo",") privileges on the host",[30,92,93,94,97],{},"Host users must already exist (e.g., ",[49,95,96],{},"sudo useradd -m username",")",[19,99,101],{"id":100},"architecture-file-structure",[23,102,103],{},"Architecture & File Structure",[15,105,106],{},"The project relies on three core files:",[108,109,110,116,122],"ol",{},[30,111,112,115],{},[49,113,114],{},"Dockerfile",": Defines the base image (Ubuntu 20.04), installs core dependencies, configures Miniconda, and sets up SSH.",[30,117,118,121],{},[49,119,120],{},"entrypoint.sh",": Injected into the container. It runs on startup to create the user, map UID\u002FGID, fix Conda permissions, and start the SSH daemon as Process 1.",[30,123,124,127],{},[49,125,126],{},"start_lab.sh",": The host-side deployment script used by the administrator to spin up new environments.",[63,129,130],{},[15,131,132],{},"Ubuntu 20.04 was used here because 22.04 was incompatible with CentOS 7.",[19,134,136],{"id":135},"deployment-workflow",[23,137,138],{},"Deployment Workflow",[140,141,143],"h3",{"id":142},"_1-prepare-the-build-directory",[23,144,145],{},"1. Prepare the Build Directory",[15,147,148,149,151,152,154,155,157],{},"Place ",[49,150,114],{},", ",[49,153,120],{},", and ",[49,156,126],{}," in the same directory on the host server.",[15,159,160],{},"Ensure the scripts are executable:",[162,163,168],"pre",{"className":164,"code":165,"language":166,"meta":167,"style":167},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","chmod +x start_lab.sh\n","bash","",[49,169,170],{"__ignoreMap":167},[171,172,175,179,183],"span",{"class":173,"line":174},"line",1,[171,176,178],{"class":177},"sBMFI","chmod",[171,180,182],{"class":181},"sfazB"," +x",[171,184,185],{"class":181}," start_lab.sh\n",[140,187,189],{"id":188},"_2-build-the-base-image",[23,190,191],{},"2. Build the Base Image",[15,193,194],{},"Run this command from the directory containing your Dockerfile:",[162,196,198],{"className":164,"code":197,"language":166,"meta":167,"style":167},"docker build -t lab_base_image .\n",[49,199,200],{"__ignoreMap":167},[171,201,202,205,208,211,214],{"class":173,"line":174},[171,203,204],{"class":177},"docker",[171,206,207],{"class":181}," build",[171,209,210],{"class":181}," -t",[171,212,213],{"class":181}," lab_base_image",[171,215,216],{"class":181}," .\n",[140,218,220],{"id":219},"_3-deploy-a-user-container",[23,221,222],{},"3. Deploy a User Container",[15,224,225,226,228],{},"Use the ",[49,227,126],{}," script to spawn a container for an existing host user. Provide the username and the designated SSH port.",[162,230,232],{"className":164,"code":231,"language":166,"meta":167,"style":167},"sudo .\u002Fstart_lab.sh \u003Cusername> \u003Cport>\n",[49,233,234],{"__ignoreMap":167},[171,235,236,238,241,245,248,252,255,257,260,263],{"class":173,"line":174},[171,237,89],{"class":177},[171,239,240],{"class":181}," .\u002Fstart_lab.sh",[171,242,244],{"class":243},"sMK4o"," \u003C",[171,246,247],{"class":181},"usernam",[171,249,251],{"class":250},"sTEyZ","e",[171,253,254],{"class":243},">",[171,256,244],{"class":243},[171,258,259],{"class":181},"por",[171,261,262],{"class":250},"t",[171,264,265],{"class":243},">\n",[140,267,269],{"id":268},"_4-configure-the-host-firewall",[23,270,271],{},"4. Configure the Host Firewall",[15,273,274,275,278],{},"CentOS uses ",[49,276,277],{},"firewalld"," by default. You must open the assigned port so the user can connect:",[162,280,282],{"className":164,"code":281,"language":166,"meta":167,"style":167},"sudo firewall-cmd --zone=public --add-port=2001\u002Ftcp --permanent\nsudo firewall-cmd --reload\n",[49,283,284,300],{"__ignoreMap":167},[171,285,286,288,291,294,297],{"class":173,"line":174},[171,287,89],{"class":177},[171,289,290],{"class":181}," firewall-cmd",[171,292,293],{"class":181}," --zone=public",[171,295,296],{"class":181}," --add-port=2001\u002Ftcp",[171,298,299],{"class":181}," --permanent\n",[171,301,303,305,307],{"class":173,"line":302},2,[171,304,89],{"class":177},[171,306,290],{"class":181},[171,308,309],{"class":181}," --reload\n",[140,311,313],{"id":312},"_5-access-the-lab",[23,314,315],{},"5. Access the Lab",[15,317,318],{},"The user can now SSH into their isolated environment from their local machine:",[162,320,322],{"className":164,"code":321,"language":166,"meta":167,"style":167},"ssh \u003Cusername>@\u003Chost_ip_address> -p \u003Cport>\n# Password defaults to: password123 (forces change\u002Fsetup later)\n",[49,323,324,362],{"__ignoreMap":167},[171,325,326,329,331,333,335,337,340,343,346,349,351,354,356,358,360],{"class":173,"line":174},[171,327,328],{"class":177},"ssh",[171,330,244],{"class":243},[171,332,247],{"class":181},[171,334,251],{"class":250},[171,336,254],{"class":243},[171,338,339],{"class":181},"@",[171,341,342],{"class":243},"\u003C",[171,344,345],{"class":181},"host_ip_addres",[171,347,348],{"class":250},"s",[171,350,254],{"class":243},[171,352,353],{"class":181}," -p",[171,355,244],{"class":243},[171,357,259],{"class":181},[171,359,262],{"class":250},[171,361,265],{"class":243},[171,363,364],{"class":173,"line":302},[171,365,367],{"class":366},"sHwdD","# Password defaults to: password123 (forces change\u002Fsetup later)\n",[19,369,371],{"id":370},"files","Files",[140,373,375],{"id":374},"entrypointsh",[49,376,120],{},[15,378,379,380,383],{},"This file is used to conduct the UID\u002FGID matching to ensure that the user can access the files inside the container. Note that the default password is set to ",[49,381,382],{},"password123"," .",[140,385],{"id":167},[162,387,389],{"className":164,"code":388,"filename":120,"language":166,"meta":167,"style":167},"#!\u002Fbin\u002Fbash\n\n# Default values if variables aren't passed\nUSER_UID=${HOST_UID:-1000}\nUSER_GID=${HOST_GID:-1000}\nUSER_NAME=${USERNAME:-labuser}\n\necho \"Configuring container for $USER_NAME (UID: $USER_UID, GID: $USER_GID)\"\n\n# 1. Create the group and user to match the host\ngroupadd -g $USER_GID $USER_NAME 2>\u002Fdev\u002Fnull || echo \"Group exists\"\nuseradd -m -u $USER_UID -g $USER_GID -s \u002Fbin\u002Fbash $USER_NAME 2>\u002Fdev\u002Fnull || echo \"User exists\"\n\n# 2. Set a default password and give sudo rights\necho \"$USER_NAME:password123\" | chpasswd\necho \"$USER_NAME ALL=(ALL) NOPASSWD:ALL\" >> \u002Fetc\u002Fsudoers\n\n# 3. Fix permissions for Conda so the user can manage their own environments\nchown -R $USER_NAME:$USER_GID \u002Fopt\u002Fconda\n\n# 4. Initialize Conda for the new user\nsudo -u $USER_NAME \u002Fopt\u002Fconda\u002Fbin\u002Fconda init bash\n\n# 5. Start the SSH service in the foreground\nexec \u002Fusr\u002Fsbin\u002Fsshd -D\n",[49,390,391,396,402,408,429,446,464,469,502,507,513,544,589,594,600,621,641,646,652,673,678,684,702,707,713],{"__ignoreMap":167},[171,392,393],{"class":173,"line":174},[171,394,395],{"class":366},"#!\u002Fbin\u002Fbash\n",[171,397,398],{"class":173,"line":302},[171,399,401],{"emptyLinePlaceholder":400},true,"\n",[171,403,405],{"class":173,"line":404},3,[171,406,407],{"class":366},"# Default values if variables aren't passed\n",[171,409,411,414,417,420,423,426],{"class":173,"line":410},4,[171,412,413],{"class":250},"USER_UID",[171,415,416],{"class":243},"=${",[171,418,419],{"class":250},"HOST_UID",[171,421,422],{"class":243},":-",[171,424,425],{"class":250},"1000",[171,427,428],{"class":243},"}\n",[171,430,432,435,437,440,442,444],{"class":173,"line":431},5,[171,433,434],{"class":250},"USER_GID",[171,436,416],{"class":243},[171,438,439],{"class":250},"HOST_GID",[171,441,422],{"class":243},[171,443,425],{"class":250},[171,445,428],{"class":243},[171,447,449,452,454,457,459,462],{"class":173,"line":448},6,[171,450,451],{"class":250},"USER_NAME",[171,453,416],{"class":243},[171,455,456],{"class":250},"USERNAME",[171,458,422],{"class":243},[171,460,461],{"class":250},"labuser",[171,463,428],{"class":243},[171,465,467],{"class":173,"line":466},7,[171,468,401],{"emptyLinePlaceholder":400},[171,470,472,476,479,482,485,488,491,494,497,499],{"class":173,"line":471},8,[171,473,475],{"class":474},"s2Zo4","echo",[171,477,478],{"class":243}," \"",[171,480,481],{"class":181},"Configuring container for ",[171,483,484],{"class":250},"$USER_NAME",[171,486,487],{"class":181}," (UID: ",[171,489,490],{"class":250},"$USER_UID",[171,492,493],{"class":181},", GID: ",[171,495,496],{"class":250},"$USER_GID",[171,498,97],{"class":181},[171,500,501],{"class":243},"\"\n",[171,503,505],{"class":173,"line":504},9,[171,506,401],{"emptyLinePlaceholder":400},[171,508,510],{"class":173,"line":509},10,[171,511,512],{"class":366},"# 1. Create the group and user to match the host\n",[171,514,516,519,522,525,528,531,534,537,539,542],{"class":173,"line":515},11,[171,517,518],{"class":177},"groupadd",[171,520,521],{"class":181}," -g",[171,523,524],{"class":250}," $USER_GID $USER_NAME ",[171,526,527],{"class":243},"2>",[171,529,530],{"class":181},"\u002Fdev\u002Fnull",[171,532,533],{"class":243}," ||",[171,535,536],{"class":474}," echo",[171,538,478],{"class":243},[171,540,541],{"class":181},"Group exists",[171,543,501],{"class":243},[171,545,547,550,553,556,559,562,565,568,571,574,576,578,580,582,584,587],{"class":173,"line":546},12,[171,548,549],{"class":177},"useradd",[171,551,552],{"class":181}," -m",[171,554,555],{"class":181}," -u",[171,557,558],{"class":250}," $USER_UID ",[171,560,561],{"class":181},"-g",[171,563,564],{"class":250}," $USER_GID ",[171,566,567],{"class":181},"-s",[171,569,570],{"class":181}," \u002Fbin\u002Fbash",[171,572,573],{"class":250}," $USER_NAME ",[171,575,527],{"class":243},[171,577,530],{"class":181},[171,579,533],{"class":243},[171,581,536],{"class":474},[171,583,478],{"class":243},[171,585,586],{"class":181},"User exists",[171,588,501],{"class":243},[171,590,592],{"class":173,"line":591},13,[171,593,401],{"emptyLinePlaceholder":400},[171,595,597],{"class":173,"line":596},14,[171,598,599],{"class":366},"# 2. Set a default password and give sudo rights\n",[171,601,603,605,607,609,612,615,618],{"class":173,"line":602},15,[171,604,475],{"class":474},[171,606,478],{"class":243},[171,608,484],{"class":250},[171,610,611],{"class":181},":password123",[171,613,614],{"class":243},"\"",[171,616,617],{"class":243}," |",[171,619,620],{"class":177}," chpasswd\n",[171,622,624,626,628,630,633,635,638],{"class":173,"line":623},16,[171,625,475],{"class":474},[171,627,478],{"class":243},[171,629,484],{"class":250},[171,631,632],{"class":181}," ALL=(ALL) NOPASSWD:ALL",[171,634,614],{"class":243},[171,636,637],{"class":243}," >>",[171,639,640],{"class":181}," \u002Fetc\u002Fsudoers\n",[171,642,644],{"class":173,"line":643},17,[171,645,401],{"emptyLinePlaceholder":400},[171,647,649],{"class":173,"line":648},18,[171,650,651],{"class":366},"# 3. Fix permissions for Conda so the user can manage their own environments\n",[171,653,655,658,661,664,667,670],{"class":173,"line":654},19,[171,656,657],{"class":177},"chown",[171,659,660],{"class":181}," -R",[171,662,663],{"class":250}," $USER_NAME",[171,665,666],{"class":181},":",[171,668,669],{"class":250},"$USER_GID ",[171,671,672],{"class":181},"\u002Fopt\u002Fconda\n",[171,674,676],{"class":173,"line":675},20,[171,677,401],{"emptyLinePlaceholder":400},[171,679,681],{"class":173,"line":680},21,[171,682,683],{"class":366},"# 4. Initialize Conda for the new user\n",[171,685,687,689,691,693,696,699],{"class":173,"line":686},22,[171,688,89],{"class":177},[171,690,555],{"class":181},[171,692,573],{"class":250},[171,694,695],{"class":181},"\u002Fopt\u002Fconda\u002Fbin\u002Fconda",[171,697,698],{"class":181}," init",[171,700,701],{"class":181}," bash\n",[171,703,705],{"class":173,"line":704},23,[171,706,401],{"emptyLinePlaceholder":400},[171,708,710],{"class":173,"line":709},24,[171,711,712],{"class":366},"# 5. Start the SSH service in the foreground\n",[171,714,716,719,722],{"class":173,"line":715},25,[171,717,718],{"class":474},"exec",[171,720,721],{"class":181}," \u002Fusr\u002Fsbin\u002Fsshd",[171,723,724],{"class":181}," -D\n",[140,726,728],{"id":727},"dockerfile",[49,729,114],{},[162,731,734],{"className":732,"code":733,"filename":114,"language":727,"meta":167,"style":167},"language-dockerfile shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","FROM ubuntu:20.04\n\n# Setup APT and Mirrors\nRUN rm -rf \u002Fetc\u002Fapt\u002Fapt.conf.d\u002F* && \\\n    echo 'APT::Get::AllowUnauthenticated \"true\";' > \u002Fetc\u002Fapt\u002Fapt.conf.d\u002F99force-insecure\nRUN echo \"deb [trusted=yes] http:\u002F\u002Fmirrors.tuna.tsinghua.edu.cn\u002Fubuntu\u002F focal main restricted universe multiverse\" > \u002Fetc\u002Fapt\u002Fsources.list\n\n# Install Core Tools and SSH\nRUN apt-get update && \\\n    DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-unauthenticated \\\n    wget ca-certificates bzip2 openssh-server sudo && \\\n    mkdir \u002Fvar\u002Frun\u002Fsshd && \\\n    apt-get clean\n\n# Install Miniconda\nRUN wget --no-check-certificate https:\u002F\u002Fmirrors.tuna.tsinghua.edu.cn\u002Fanaconda\u002Fminiconda\u002FMiniconda3-py39_4.12.0-Linux-x86_64.sh -O \u002Ftmp\u002Fminiconda.sh && \\\n    bash \u002Ftmp\u002Fminiconda.sh -b -p \u002Fopt\u002Fconda && \\\n    rm \u002Ftmp\u002Fminiconda.sh\n\nENV PATH=\"\u002Fopt\u002Fconda\u002Fbin:$PATH\"\n\n# Copy the entrypoint script from your host into the image\nCOPY entrypoint.sh \u002Fusr\u002Flocal\u002Fbin\u002Fentrypoint.sh\nRUN chmod +x \u002Fusr\u002Flocal\u002Fbin\u002Fentrypoint.sh\n\nEXPOSE 22\n\n# Tell Docker to run our script on startup\nENTRYPOINT [\"\u002Fusr\u002Flocal\u002Fbin\u002Fentrypoint.sh\"]\n",[49,735,736,741,745,750,755,760,765,769,774,779,784,789,794,799,803,808,813,818,823,827,832,836,841,846,851,855,861,866,872],{"__ignoreMap":167},[171,737,738],{"class":173,"line":174},[171,739,740],{},"FROM ubuntu:20.04\n",[171,742,743],{"class":173,"line":302},[171,744,401],{"emptyLinePlaceholder":400},[171,746,747],{"class":173,"line":404},[171,748,749],{},"# Setup APT and Mirrors\n",[171,751,752],{"class":173,"line":410},[171,753,754],{},"RUN rm -rf \u002Fetc\u002Fapt\u002Fapt.conf.d\u002F* && \\\n",[171,756,757],{"class":173,"line":431},[171,758,759],{},"    echo 'APT::Get::AllowUnauthenticated \"true\";' > \u002Fetc\u002Fapt\u002Fapt.conf.d\u002F99force-insecure\n",[171,761,762],{"class":173,"line":448},[171,763,764],{},"RUN echo \"deb [trusted=yes] http:\u002F\u002Fmirrors.tuna.tsinghua.edu.cn\u002Fubuntu\u002F focal main restricted universe multiverse\" > \u002Fetc\u002Fapt\u002Fsources.list\n",[171,766,767],{"class":173,"line":466},[171,768,401],{"emptyLinePlaceholder":400},[171,770,771],{"class":173,"line":471},[171,772,773],{},"# Install Core Tools and SSH\n",[171,775,776],{"class":173,"line":504},[171,777,778],{},"RUN apt-get update && \\\n",[171,780,781],{"class":173,"line":509},[171,782,783],{},"    DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-unauthenticated \\\n",[171,785,786],{"class":173,"line":515},[171,787,788],{},"    wget ca-certificates bzip2 openssh-server sudo && \\\n",[171,790,791],{"class":173,"line":546},[171,792,793],{},"    mkdir \u002Fvar\u002Frun\u002Fsshd && \\\n",[171,795,796],{"class":173,"line":591},[171,797,798],{},"    apt-get clean\n",[171,800,801],{"class":173,"line":596},[171,802,401],{"emptyLinePlaceholder":400},[171,804,805],{"class":173,"line":602},[171,806,807],{},"# Install Miniconda\n",[171,809,810],{"class":173,"line":623},[171,811,812],{},"RUN wget --no-check-certificate https:\u002F\u002Fmirrors.tuna.tsinghua.edu.cn\u002Fanaconda\u002Fminiconda\u002FMiniconda3-py39_4.12.0-Linux-x86_64.sh -O \u002Ftmp\u002Fminiconda.sh && \\\n",[171,814,815],{"class":173,"line":643},[171,816,817],{},"    bash \u002Ftmp\u002Fminiconda.sh -b -p \u002Fopt\u002Fconda && \\\n",[171,819,820],{"class":173,"line":648},[171,821,822],{},"    rm \u002Ftmp\u002Fminiconda.sh\n",[171,824,825],{"class":173,"line":654},[171,826,401],{"emptyLinePlaceholder":400},[171,828,829],{"class":173,"line":675},[171,830,831],{},"ENV PATH=\"\u002Fopt\u002Fconda\u002Fbin:$PATH\"\n",[171,833,834],{"class":173,"line":680},[171,835,401],{"emptyLinePlaceholder":400},[171,837,838],{"class":173,"line":686},[171,839,840],{},"# Copy the entrypoint script from your host into the image\n",[171,842,843],{"class":173,"line":704},[171,844,845],{},"COPY entrypoint.sh \u002Fusr\u002Flocal\u002Fbin\u002Fentrypoint.sh\n",[171,847,848],{"class":173,"line":709},[171,849,850],{},"RUN chmod +x \u002Fusr\u002Flocal\u002Fbin\u002Fentrypoint.sh\n",[171,852,853],{"class":173,"line":715},[171,854,401],{"emptyLinePlaceholder":400},[171,856,858],{"class":173,"line":857},26,[171,859,860],{},"EXPOSE 22\n",[171,862,864],{"class":173,"line":863},27,[171,865,401],{"emptyLinePlaceholder":400},[171,867,869],{"class":173,"line":868},28,[171,870,871],{},"# Tell Docker to run our script on startup\n",[171,873,875],{"class":173,"line":874},29,[171,876,877],{},"ENTRYPOINT [\"\u002Fusr\u002Flocal\u002Fbin\u002Fentrypoint.sh\"]\n",[63,879,880],{},[15,881,882],{},"Note that the Tsinghua mirror is used.",[140,884,886],{"id":885},"start_labsh",[49,887,126],{},[15,889,890,891,894,895,898],{},"Note that the host ",[49,892,893],{},"UID"," and ",[49,896,897],{},"GID"," are fetched so that matching can be made. ",[162,900,902],{"className":164,"code":901,"filename":126,"language":166,"meta":167,"style":167},"#!\u002Fbin\u002Fbash\n\n# Usage: .\u002Fstart_lab.sh \u003Chost_username> \u003Cexternal_port>\nTARGET_USER=$1\nPORT=$2\n\nif [ -z \"$TARGET_USER\" ] || [ -z \"$PORT\" ]; then\n    echo \"Usage: .\u002Fstart_lab.sh \u003Cusername> \u003Cport>\"\n    exit 1\nfi\n\n# Get IDs from the host system\nU_ID=$(id -u $TARGET_USER)\nG_ID=$(id -g $TARGET_USER)\nU_HOME=\"\u002Fhome\u002F$TARGET_USER\"\n\n# Ensure the host directory exists\nmkdir -p \"$U_HOME\"\n\n# Change ownership to the target user \n# (This assumes the user exists on the CentOS host with the same name)\nchown -R \"$TARGET_USER:$TARGET_USER\" \"$U_HOME\"\n\n# Build\u002FRefresh the image (optional, but ensures entrypoint updates are live)\n# docker build -t lab_base_image .\n\ndocker run -d \\\n  --name \"lab_$TARGET_USER\" \\\n  --restart unless-stopped \\\n  -e HOST_UID=$U_ID \\\n  -e HOST_GID=$G_ID \\\n  -e USERNAME=$TARGET_USER \\\n  -p \"$PORT:22\" \\\n  -v \"$U_HOME:\u002Fhome\u002F$TARGET_USER:z\" \\\n  lab_base_image\n\necho \"----------------------------------------------------\"\necho \"Container for $TARGET_USER is now LIVE.\"\necho \"Access via: ssh $TARGET_USER@$(hostname -I | awk '{print $1}') -p $PORT\"\necho \"Work directory mapped to: $U_HOME\"\necho \"----------------------------------------------------\"\n",[49,903,904,908,912,917,929,939,943,984,996,1005,1010,1014,1019,1038,1053,1069,1073,1078,1092,1096,1101,1106,1128,1132,1137,1142,1146,1159,1175,1185,1197,1208,1219,1236,1258,1264,1269,1281,1298,1343,1357],{"__ignoreMap":167},[171,905,906],{"class":173,"line":174},[171,907,395],{"class":366},[171,909,910],{"class":173,"line":302},[171,911,401],{"emptyLinePlaceholder":400},[171,913,914],{"class":173,"line":404},[171,915,916],{"class":366},"# Usage: .\u002Fstart_lab.sh \u003Chost_username> \u003Cexternal_port>\n",[171,918,919,922,925],{"class":173,"line":410},[171,920,921],{"class":250},"TARGET_USER",[171,923,924],{"class":243},"=",[171,926,928],{"class":927},"sHdIc","$1\n",[171,930,931,934,936],{"class":173,"line":431},[171,932,933],{"class":250},"PORT",[171,935,924],{"class":243},[171,937,938],{"class":927},"$2\n",[171,940,941],{"class":173,"line":448},[171,942,401],{"emptyLinePlaceholder":400},[171,944,945,949,952,955,957,960,962,965,967,969,971,973,976,978,981],{"class":173,"line":466},[171,946,948],{"class":947},"s7zQu","if",[171,950,951],{"class":243}," [",[171,953,954],{"class":243}," -z",[171,956,478],{"class":243},[171,958,959],{"class":250},"$TARGET_USER",[171,961,614],{"class":243},[171,963,964],{"class":243}," ]",[171,966,533],{"class":243},[171,968,951],{"class":243},[171,970,954],{"class":243},[171,972,478],{"class":243},[171,974,975],{"class":250},"$PORT",[171,977,614],{"class":243},[171,979,980],{"class":243}," ];",[171,982,983],{"class":947}," then\n",[171,985,986,989,991,994],{"class":173,"line":471},[171,987,988],{"class":474},"    echo",[171,990,478],{"class":243},[171,992,993],{"class":181},"Usage: .\u002Fstart_lab.sh \u003Cusername> \u003Cport>",[171,995,501],{"class":243},[171,997,998,1001],{"class":173,"line":504},[171,999,1000],{"class":474},"    exit",[171,1002,1004],{"class":1003},"sbssI"," 1\n",[171,1006,1007],{"class":173,"line":509},[171,1008,1009],{"class":947},"fi\n",[171,1011,1012],{"class":173,"line":515},[171,1013,401],{"emptyLinePlaceholder":400},[171,1015,1016],{"class":173,"line":546},[171,1017,1018],{"class":366},"# Get IDs from the host system\n",[171,1020,1021,1024,1027,1030,1032,1035],{"class":173,"line":591},[171,1022,1023],{"class":250},"U_ID",[171,1025,1026],{"class":243},"=$(",[171,1028,1029],{"class":177},"id",[171,1031,555],{"class":181},[171,1033,1034],{"class":250}," $TARGET_USER",[171,1036,1037],{"class":243},")\n",[171,1039,1040,1043,1045,1047,1049,1051],{"class":173,"line":596},[171,1041,1042],{"class":250},"G_ID",[171,1044,1026],{"class":243},[171,1046,1029],{"class":177},[171,1048,521],{"class":181},[171,1050,1034],{"class":250},[171,1052,1037],{"class":243},[171,1054,1055,1058,1060,1062,1065,1067],{"class":173,"line":602},[171,1056,1057],{"class":250},"U_HOME",[171,1059,924],{"class":243},[171,1061,614],{"class":243},[171,1063,1064],{"class":181},"\u002Fhome\u002F",[171,1066,959],{"class":250},[171,1068,501],{"class":243},[171,1070,1071],{"class":173,"line":623},[171,1072,401],{"emptyLinePlaceholder":400},[171,1074,1075],{"class":173,"line":643},[171,1076,1077],{"class":366},"# Ensure the host directory exists\n",[171,1079,1080,1083,1085,1087,1090],{"class":173,"line":648},[171,1081,1082],{"class":177},"mkdir",[171,1084,353],{"class":181},[171,1086,478],{"class":243},[171,1088,1089],{"class":250},"$U_HOME",[171,1091,501],{"class":243},[171,1093,1094],{"class":173,"line":654},[171,1095,401],{"emptyLinePlaceholder":400},[171,1097,1098],{"class":173,"line":675},[171,1099,1100],{"class":366},"# Change ownership to the target user \n",[171,1102,1103],{"class":173,"line":680},[171,1104,1105],{"class":366},"# (This assumes the user exists on the CentOS host with the same name)\n",[171,1107,1108,1110,1112,1114,1116,1118,1120,1122,1124,1126],{"class":173,"line":686},[171,1109,657],{"class":177},[171,1111,660],{"class":181},[171,1113,478],{"class":243},[171,1115,959],{"class":250},[171,1117,666],{"class":181},[171,1119,959],{"class":250},[171,1121,614],{"class":243},[171,1123,478],{"class":243},[171,1125,1089],{"class":250},[171,1127,501],{"class":243},[171,1129,1130],{"class":173,"line":704},[171,1131,401],{"emptyLinePlaceholder":400},[171,1133,1134],{"class":173,"line":709},[171,1135,1136],{"class":366},"# Build\u002FRefresh the image (optional, but ensures entrypoint updates are live)\n",[171,1138,1139],{"class":173,"line":715},[171,1140,1141],{"class":366},"# docker build -t lab_base_image .\n",[171,1143,1144],{"class":173,"line":857},[171,1145,401],{"emptyLinePlaceholder":400},[171,1147,1148,1150,1153,1156],{"class":173,"line":863},[171,1149,204],{"class":177},[171,1151,1152],{"class":181}," run",[171,1154,1155],{"class":181}," -d",[171,1157,1158],{"class":250}," \\\n",[171,1160,1161,1164,1166,1169,1171,1173],{"class":173,"line":868},[171,1162,1163],{"class":181},"  --name",[171,1165,478],{"class":243},[171,1167,1168],{"class":181},"lab_",[171,1170,959],{"class":250},[171,1172,614],{"class":243},[171,1174,1158],{"class":250},[171,1176,1177,1180,1183],{"class":173,"line":874},[171,1178,1179],{"class":181},"  --restart",[171,1181,1182],{"class":181}," unless-stopped",[171,1184,1158],{"class":250},[171,1186,1188,1191,1194],{"class":173,"line":1187},30,[171,1189,1190],{"class":181},"  -e",[171,1192,1193],{"class":181}," HOST_UID=",[171,1195,1196],{"class":250},"$U_ID \\\n",[171,1198,1200,1202,1205],{"class":173,"line":1199},31,[171,1201,1190],{"class":181},[171,1203,1204],{"class":181}," HOST_GID=",[171,1206,1207],{"class":250},"$G_ID \\\n",[171,1209,1211,1213,1216],{"class":173,"line":1210},32,[171,1212,1190],{"class":181},[171,1214,1215],{"class":181}," USERNAME=",[171,1217,1218],{"class":250},"$TARGET_USER \\\n",[171,1220,1222,1225,1227,1229,1232,1234],{"class":173,"line":1221},33,[171,1223,1224],{"class":181},"  -p",[171,1226,478],{"class":243},[171,1228,975],{"class":250},[171,1230,1231],{"class":181},":22",[171,1233,614],{"class":243},[171,1235,1158],{"class":250},[171,1237,1239,1242,1244,1246,1249,1251,1254,1256],{"class":173,"line":1238},34,[171,1240,1241],{"class":181},"  -v",[171,1243,478],{"class":243},[171,1245,1089],{"class":250},[171,1247,1248],{"class":181},":\u002Fhome\u002F",[171,1250,959],{"class":250},[171,1252,1253],{"class":181},":z",[171,1255,614],{"class":243},[171,1257,1158],{"class":250},[171,1259,1261],{"class":173,"line":1260},35,[171,1262,1263],{"class":181},"  lab_base_image\n",[171,1265,1267],{"class":173,"line":1266},36,[171,1268,401],{"emptyLinePlaceholder":400},[171,1270,1272,1274,1276,1279],{"class":173,"line":1271},37,[171,1273,475],{"class":474},[171,1275,478],{"class":243},[171,1277,1278],{"class":181},"----------------------------------------------------",[171,1280,501],{"class":243},[171,1282,1284,1286,1288,1291,1293,1296],{"class":173,"line":1283},38,[171,1285,475],{"class":474},[171,1287,478],{"class":243},[171,1289,1290],{"class":181},"Container for ",[171,1292,959],{"class":250},[171,1294,1295],{"class":181}," is now LIVE.",[171,1297,501],{"class":243},[171,1299,1301,1303,1305,1308,1310,1312,1315,1318,1321,1324,1327,1330,1333,1336,1339,1341],{"class":173,"line":1300},39,[171,1302,475],{"class":474},[171,1304,478],{"class":243},[171,1306,1307],{"class":181},"Access via: ssh ",[171,1309,959],{"class":250},[171,1311,339],{"class":181},[171,1313,1314],{"class":243},"$(",[171,1316,1317],{"class":177},"hostname",[171,1319,1320],{"class":181}," -I ",[171,1322,1323],{"class":243},"|",[171,1325,1326],{"class":177}," awk",[171,1328,1329],{"class":243}," '",[171,1331,1332],{"class":181},"{print $1}",[171,1334,1335],{"class":243},"')",[171,1337,1338],{"class":181}," -p ",[171,1340,975],{"class":250},[171,1342,501],{"class":243},[171,1344,1346,1348,1350,1353,1355],{"class":173,"line":1345},40,[171,1347,475],{"class":474},[171,1349,478],{"class":243},[171,1351,1352],{"class":181},"Work directory mapped to: ",[171,1354,1089],{"class":250},[171,1356,501],{"class":243},[171,1358,1360,1362,1364,1366],{"class":173,"line":1359},41,[171,1361,475],{"class":474},[171,1363,478],{"class":243},[171,1365,1278],{"class":181},[171,1367,501],{"class":243},[19,1369,1371],{"id":1370},"troubleshooting",[23,1372,1373],{},"Troubleshooting",[27,1375,1376,1394],{},[30,1377,1378,1381,1382,1385,1386,1389,1390,1393],{},[23,1379,1380],{},"\"Command Not Found\" when running scripts:"," Ensure you are using ",[49,1383,1384],{},".\u002F"," to execute scripts in the current directory (e.g., ",[49,1387,1388],{},".\u002Fstart_lab.sh",") and that the file has execution permissions (",[49,1391,1392],{},"chmod +x",").",[30,1395,1396,1399],{},[23,1397,1398],{},"Connection Refused on SSH:",[108,1400,1401,1407,1413],{},[30,1402,1403,1404],{},"Verify the container is running: ",[49,1405,1406],{},"docker ps",[30,1408,1409,1410],{},"Verify the SSH service is active inside the container: ",[49,1411,1412],{},"docker exec \u003Ccontainer_name> netstat -tulpn | grep 22",[30,1414,1415],{},"Ensure the host firewall has the specific port opened (see step 4 above).",[19,1417,1419],{"id":1418},"disclaimer","Disclaimer",[15,1421,1422],{},"This project was written under AI assistance.",[15,1424,1425,1428],{},[23,1426,1427],{},"This code is provided \"as-is\" without any warranty."," Users are solely responsible for validating results for their specific applications. The author(s) are not liable for any errors, inaccuracies, or damages arising from the use of this software.",[15,1430,1431],{},"For critical research applications, please independently verify all measurements and consult established methodologies in the field.",[1433,1434,1435],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":167,"searchDepth":302,"depth":302,"links":1437},[1438,1439,1440,1441,1448,1454,1455],{"id":21,"depth":302,"text":25},{"id":69,"depth":302,"text":72},{"id":100,"depth":302,"text":103},{"id":135,"depth":302,"text":138,"children":1442},[1443,1444,1445,1446,1447],{"id":142,"depth":404,"text":145},{"id":188,"depth":404,"text":191},{"id":219,"depth":404,"text":222},{"id":268,"depth":404,"text":271},{"id":312,"depth":404,"text":315},{"id":370,"depth":302,"text":371,"children":1449},[1450,1451,1452,1453],{"id":374,"depth":404,"text":120},{"id":167,"depth":404,"text":167},{"id":727,"depth":404,"text":114},{"id":885,"depth":404,"text":126},{"id":1370,"depth":302,"text":1373},{"id":1418,"depth":302,"text":1419},"2026-04-22","A streamlined procedure for managing user environments as docker images in a small team-based environment on a shared server. ","md",null,{},{"title":167},"\u002Fprojects\u002Fmulti-user-docker-lab-management",{"showWiki":1464,"customWikiLink":167},false,{"title":5,"description":1457},"projects\u002Fmulti-user-docker-lab-management","rE3TeNl3W1_5kUMfAE_ueqoWY59raPfEY77HbYTk3Cc",[1459,1469],{"title":1470,"path":1471,"stem":1472,"children":-1},"Wireless Motion Controlled Side Show Clicker","\u002Fprojects\u002Fwireless-motion-controlled-slide-show-clicker","projects\u002Fwireless-motion-controlled-slide-show-clicker",1777421406101]