1 · Words 2 · Machines 3 · Two Linuxes 4 · Command line 5 · Storage 6 · Networking 7 · vim 8 · SSH

🐧 Linux Lab

Most computers on the internet run Linux. Not one Linux — many. Each one is called a distribution, and each one puts its files in slightly different places and installs programs with a different command. Two families cover most of the world: the Debian family, where Ubuntu lives, and the Red Hat family, where Rocky Linux lives. 互联网上大多数计算机跑的是 Linux。不是一个 Linux,而是很多个。 每一个叫做一个发行版,各自把文件放在略微不同的地方,安装程序也用不同的命令。 两大家族覆盖了世界上大部分:Debian 家族(Ubuntu 在里面)和 Red Hat 家族(Rocky Linux 在里面)。 คอมพิวเตอร์ส่วนใหญ่บนอินเทอร์เน็ตรัน Linux ไม่ใช่ Linux ตัวเดียว แต่มีหลายตัว แต่ละตัวเรียกว่า distribution และแต่ละตัววางไฟล์ไว้ต่างกันเล็กน้อย และติดตั้งโปรแกรมด้วยคำสั่งที่ต่างกัน สองตระกูลครอบคลุมเกือบทั้งโลก: ตระกูล Debian ที่มี Ubuntu อยู่ และตระกูล Red Hat ที่มี Rocky Linux อยู่

Learning both used to mean two computers. Now it means two commands. Docker gives you a clean Ubuntu and a clean Rocky in about one second each, and you can throw them away when you break them. That is the best way to learn Linux that has ever existed. 以前想两个都学,得有两台电脑;现在只要两条命令。 Docker 大约一秒钟就给你一个干净的 Ubuntu 和一个干净的 Rocky,弄坏了直接扔掉。 这是有史以来学 Linux 最好的方式。 เมื่อก่อนจะเรียนทั้งสองตัวต้องมีคอมสองเครื่อง ตอนนี้ใช้แค่สองคำสั่ง Docker ให้ Ubuntu สะอาด ๆ และ Rocky สะอาด ๆ ในเวลาประมาณหนึ่งวินาทีต่อตัว พังแล้วก็ทิ้งได้เลย นี่คือวิธีเรียน Linux ที่ดีที่สุดที่เคยมีมา

🐳 You need Docker first. This page assumes Docker is installed and the whale is green. If it is not, the Docker Lab installs it on Windows or a Mac in about fifteen minutes.
你需要先装好 Docker。本页假设 Docker 已安装、鲸鱼是绿的。 如果还没有,Docker 实验室会教你在 Windows 或 Mac 上装好,大约十五分钟。

คุณต้องมี Docker ก่อน หน้านี้ถือว่าติดตั้ง Docker แล้วและรูปวาฬเป็นสีเขียว ถ้ายังไม่มี แล็บ Docker จะสอนติดตั้งบน Windows หรือ Mac ในเวลาประมาณสิบห้านาที
Open ▸

Today you will learn the words, drive four machines, run both Linuxes side by side, learn the command line, keep files that survive, make two containers talk to each other, and finally leave vim — with a real vim you can practise right here in the page. 今天你会学词、玩四台机器、把两个 Linux 并排跑起来、学命令行、 保住那些不该丢的文件、让两个容器互相通话,最后学会退出 vim —— 页面里就有一个真的 vim 让你练。 วันนี้คุณจะเรียนคำศัพท์ เล่นเครื่องมือสี่ตัว รัน Linux ทั้งสองตัวเทียบกัน เรียนคอมมานด์ไลน์ เก็บไฟล์ให้อยู่รอด ทำให้สองคอนเทนเนอร์คุยกัน และสุดท้ายออกจาก vim ให้ได้ — มี vim จริงให้ฝึกในหน้านี้เลย

Which two, and why. We use Ubuntu 24.04 and Rocky Linux 9. Ubuntu is the one most tutorials assume. Rocky is the free, living stand-in for Red Hat — it replaced CentOS, which ended in June 2024. Learn these two and you can work on almost any Linux server in the world. 为什么是这两个。我们用 Ubuntu 24.04Rocky Linux 9。 Ubuntu 是大多数教程默认的那个;Rocky 是 Red Hat 免费且还活着的替身—— 它接替了 2024 年 6 月结束的 CentOS。学会这两个,世界上几乎任何 Linux 服务器你都能上手。 ทำไมเลือกสองตัวนี้ เราใช้ Ubuntu 24.04 และ Rocky Linux 9 Ubuntu คือตัวที่บทเรียนส่วนใหญ่ถือว่าคุณใช้ ส่วน Rocky คือตัวแทนของ Red Hat ที่ฟรีและยังมีชีวิต — มันมาแทน CentOS ที่จบไปเมื่อมิถุนายน 2024 เรียนสองตัวนี้แล้วคุณทำงานกับเซิร์ฟเวอร์ Linux เกือบทุกเครื่องในโลกได้

🔤 The words you need

Summary

Twenty-two words. These are the words a Linux job is done in. They are the same on Ubuntu and on Rocky, even when the commands are different. You will type nearly all of them today. Tap say it to hear one word. Tap tell me more to read a long answer.

摘要

二十二个词。做 Linux 这份工作就是用这些词。就算命令不同,这些词在 Ubuntu 和 Rocky 上是一样的。今天你几乎都会亲手敲一遍。点 say it 听发音,点 tell me more 读详细解释。

สรุป

ยี่สิบสองคำ นี่คือคำที่ใช้ทำงาน Linux คำเหล่านี้เหมือนกันทั้งบน Ubuntu และ Rocky แม้ว่าคำสั่งจะต่างกัน วันนี้คุณจะได้พิมพ์เกือบทุกคำ แตะ say it เพื่อฟังหนึ่งคำ แตะ tell me more เพื่ออ่านคำอธิบายยาว

🔧 Four little machines

Each machine shows a real command on the left and what it does on the right. Click the chips. Press the buttons. Every command here was run in a real container before it went on this page. 每台机器左边是真实命令,右边是它做的事。点小标签、按按钮。 这页上每一条命令,都先在真实容器里跑过才写上来。 แต่ละเครื่องแสดงคำสั่งจริงทางซ้าย และผลของมันทางขวา กดชิป กดปุ่ม ทุกคำสั่งในหน้านี้ถูกรันในคอนเทนเนอร์จริงมาก่อนแล้ว

🔄 1 · The same job, two commands

Summary

Ubuntu and Rocky do the same jobs with different words. Ubuntu uses apt and packages called deb. Rocky uses dnf, or the older yum, and packages called rpm. Once you know the pairs, you can work on either one. Click a job and read both commands.

摘要

Ubuntu 和 Rocky 做同样的事,用的词不一样。Ubuntu 用 apt,包叫 deb;Rocky 用 dnf(或者老一点的 yum),包叫 rpm。你把这些对子记住了,两边都能干活。点一个任务,把两条命令都读一遍。

สรุป

Ubuntu กับ Rocky ทำงานเดียวกันด้วยคำที่ต่างกัน Ubuntu ใช้ apt แพ็กเกจชื่อ deb ส่วน Rocky ใช้ dnf หรือ yum ตัวเก่ากว่า แพ็กเกจชื่อ rpm พอคุณรู้ว่าคำไหนคู่กับคำไหน คุณก็ทำงานได้ทั้งสองฝั่ง คลิกงานหนึ่งงานแล้วอ่านทั้งสองคำสั่ง

Pick the job you want to do. 选一个你想做的事。 เลือกงานที่คุณต้องการทำ

two families, one idea

🐧 Two Linuxes, side by side

Summary

Open two terminal windows. Put Ubuntu in one and Rocky in the other. Now you can run the same job twice and watch the difference with your own eyes. This is the fastest way to learn both. Each container starts in about one second, and you can delete it without fear.

摘要

开两个终端窗口,一个放 Ubuntu,一个放 Rocky。现在同一件事你可以做两遍,亲眼看差别。这是同时学会两个最快的办法。每个容器大约一秒就起来,删掉也不用心疼。

สรุป

เปิดหน้าต่างเทอร์มินัลสองบาน ใส่ Ubuntu ไว้บานหนึ่ง Rocky อีกบานหนึ่ง ตอนนี้คุณทำงานเดียวกันสองครั้งและเห็นความต่างด้วยตาตัวเองได้ นี่คือวิธีเรียนทั้งสองตัวที่เร็วที่สุด แต่ละคอนเทนเนอร์เริ่มในเวลาประมาณหนึ่งวินาที และลบทิ้งได้โดยไม่ต้องกลัว

1 Start them both

Two terminal windows. One command in each. -it means "let me type in it". 两个终端窗口,各敲一条命令。-it 的意思是“让我能在里面打字”。 หน้าต่างเทอร์มินัลสองบาน บานละหนึ่งคำสั่ง -it แปลว่า "ให้ฉันพิมพ์ในนั้นได้"

# window 1 — Ubuntu docker run -it --name ubu ubuntu:24.04 bash # window 2 — Rocky Linux docker run -it --name rocky rockylinux:9 bash

The prompt changes. You are inside now. The name after the @ is the container's id. 提示符变了,你已经在里面了。@ 后面那串就是容器的 id。 พรอมต์เปลี่ยนไป คุณอยู่ข้างในแล้ว ข้อความหลัง @ คือ id ของคอนเทนเนอร์

root@3f9c1a7b2e04:/# cat /etc/os-release | head -1 PRETTY_NAME="Ubuntu 24.04.4 LTS" [root@8b21d4e6c5f9 /]# cat /etc/redhat-release Rocky Linux release 9.3 (Blue Onyx)
Look at the two prompts. Ubuntu writes root@id:/#. Rocky writes [root@id /]# with square brackets. That tiny difference is often the fastest way to tell which window you are typing in. 看这两个提示符:Ubuntu 写成 root@id:/#, Rocky 写成带方括号的 [root@id /]#。 这个小差别常常是最快分辨你正在哪个窗口打字的办法。 ดูพรอมต์สองอันนี้ Ubuntu เขียนว่า root@id:/# Rocky เขียนว่า [root@id /]# มีวงเล็บเหลี่ยม ความต่างเล็ก ๆ นี้มักเป็นวิธีที่เร็วที่สุดในการบอกว่าคุณกำลังพิมพ์อยู่ในหน้าต่างไหน

2 A shock: both images are nearly empty — but in different ways

Try to open a file with vim. Try to ping something. Both say command not found. Container images are stripped down to stay small. What surprises people is that the two distributions strip different things out. This table was made by asking both images the same question: 试着用 vim 打开文件、试着 ping 一下——两边都会说 command not found。 容器镜像为了小,被削得很干净。让人意外的是:两个发行版削掉的东西不一样。 下面这张表是向两个镜像问同一个问题得到的: ลองเปิดไฟล์ด้วย vim ลอง ping ดู ทั้งสองฝั่งจะขึ้นว่า command not found อิมเมจคอนเทนเนอร์ถูกตัดให้เล็กที่สุด สิ่งที่ทำให้คนแปลกใจคือสองดิสโทรตัดของคนละอย่าง ตารางนี้ได้มาจากการถามอิมเมจทั้งสองด้วยคำถามเดียวกัน

Is it there?ubuntu:24.04rockylinux:9
vim✖ no✖ no
vi✖ no✔ yes
curl✖ no✔ yes
less✖ no✔ yes
ps✔ yes✖ no
which✔ yes✖ no
ping · ip · nano · sudo✖ no✖ no
Rocky gives you an editor; Ubuntu does not. On Rocky you can type vi and start work immediately. On Ubuntu there is no editor at all — not even vi — so the very first thing you do is install one. Neither image has sudo, and neither has ping. Rocky 自带编辑器,Ubuntu 没有。在 Rocky 上你敲 vi 就能立刻干活;在 Ubuntu 上一个编辑器都没有,连 vi 都没有, 所以你要做的第一件事就是装一个。两边都没有 sudo,也都没有 ping Rocky ให้เอดิเตอร์มาด้วย Ubuntu ไม่ให้ บน Rocky พิมพ์ vi แล้วเริ่มทำงานได้ทันที บน Ubuntu ไม่มีเอดิเตอร์เลย แม้แต่ vi สิ่งแรกที่คุณต้องทำจึงคือติดตั้งสักตัว ทั้งสองฝั่งไม่มี sudo และไม่มี ping
root@3f9c1a7b2e04:/# vim /etc/hostname bash: vim: command not found root@3f9c1a7b2e04:/# ping google.com bash: ping: command not found

3 The first command in each window

On Ubuntu you must fetch the shop list first. On Rocky you do not — dnf refreshes by itself. 在 Ubuntu 上你必须先取回商店清单;在 Rocky 上不用——dnf 会自己刷新。 บน Ubuntu คุณต้องไปเอารายการสินค้ามาก่อน บน Rocky ไม่ต้อง — dnf รีเฟรชเอง

# window 1 — Ubuntu apt-get update apt-get install -y vim iputils-ping iproute2 curl less
# window 2 — Rocky. No update step. Note the package names. dnf install -y vim-enhanced iputils iproute

Three package names are different for the same three programs. That is most of what "knowing two distributions" really means. 同样三个程序,包名有三个不一样。所谓“懂两个发行版”,大部分就是这种事。 โปรแกรมสามตัวเดียวกัน แต่ชื่อแพ็กเกจต่างกันสามชื่อ นั่นคือความหมายส่วนใหญ่ของคำว่า "รู้สองดิสโทร"

[root@8b21d4e6c5f9 /]# dnf install -y vim-enhanced Complete! [root@8b21d4e6c5f9 /]# vim --version | head -1 VIM - Vi IMproved 8.2 (2019 Dec 12) ← no fixing, no workaround, it just works
Forget update on Ubuntu and install will fail. apt-get install reads a shop list that a fresh image does not have yet. The error says Unable to locate package, which sounds like the package does not exist. It does exist. You just never asked for the list. 在 Ubuntu 上忘了 updateinstall 就会失败。 apt-get install 要读一份商店清单,而新镜像里还没有。 报错写着 Unable to locate package,听起来像这个包不存在——它存在, 只是你没先取清单。 ถ้าลืม update บน Ubuntu แล้ว install จะล้มเหลว apt-get install ต้องอ่านรายการสินค้าที่อิมเมจใหม่ยังไม่มี ข้อความผิดพลาดเขียนว่า Unable to locate package ซึ่งฟังเหมือนว่าไม่มีแพ็กเกจนี้ มันมีอยู่ คุณแค่ยังไม่ได้ขอรายการ

4 How a script knows which family it is standing on

You will write scripts that must work on both. Do not guess from the name — read /etc/os-release, which every modern distribution has. The useful line is ID_LIKE: it names the family, so your script keeps working on Alma, Fedora or Debian too. This is real output from both containers: 你会写需要在两边都能跑的脚本。不要靠名字猜——读 /etc/os-release, 每个现代发行版都有。有用的是 ID_LIKE 那一行:它写的是家族, 所以你的脚本在 Alma、Fedora 或 Debian 上也照样能用。下面是两个容器的真实输出: คุณจะเขียนสคริปต์ที่ต้องทำงานได้ทั้งสองฝั่ง อย่าเดาจากชื่อ — ให้อ่าน /etc/os-release ซึ่งทุกดิสโทรสมัยใหม่มี บรรทัดที่มีประโยชน์คือ ID_LIKE มันบอกตระกูล สคริปต์ของคุณจึงยังทำงานได้บน Alma, Fedora หรือ Debian ด้วย นี่คือผลลัพธ์จริงจากทั้งสองคอนเทนเนอร์

$ . /etc/os-release; echo "$ID | $ID_LIKE | $VERSION_ID" rocky | rhel centos fedora | 9.3 ubuntu | debian | 24.04

Notice that Rocky still says centos in its family list, and that /etc/redhat-release exists on Rocky and reads Rocky Linux release 9.3 (Blue Onyx). That is deliberate: old scripts written for Red Hat and CentOS keep working without a single change. 注意 Rocky 的家族列表里仍然写着 centos, 而且 Rocky 上有 /etc/redhat-release,内容是 Rocky Linux release 9.3 (Blue Onyx)。这是故意的: 为 Red Hat 和 CentOS 写的老脚本一行都不用改,照样能跑。 สังเกตว่า Rocky ยังเขียนว่า centos ในรายการตระกูลของมัน และบน Rocky มี /etc/redhat-release ซึ่งเขียนว่า Rocky Linux release 9.3 (Blue Onyx) นี่เป็นความตั้งใจ: สคริปต์เก่าที่เขียนให้ Red Hat และ CentOS ยังทำงานได้โดยไม่ต้องแก้แม้แต่บรรทัดเดียว

# one script, both families if [ -f /etc/debian_version ]; then apt-get update && apt-get install -y vim else dnf install -y vim-enhanced fi

The translation table — print this one

The job Ubuntu · Debian family Rocky Linux · Red Hat family
Which Linux is this?cat /etc/os-releasecat /etc/redhat-release
Refresh the shop listapt-get updatednf check-update
Install a programapt-get install -y vimdnf install -y vim-enhanced
Remove a programapt-get remove -y vimdnf remove -y vim-enhanced
Search for a programapt-cache search editordnf search editor
What is installed?dpkg -lrpm -qa
Which package owns this file?dpkg -S /usr/bin/vimrpm -qf /usr/bin/vim
The package file itselfsomething.debsomething.rpm
Where the shop list lives/etc/apt/sources.list.d//etc/yum.repos.d/
Network tools packageiproute2 · iputils-pingiproute · iputils
Add a normal useradduser ployuseradd ploy
The group that grants sudosudowheel
Read the system log/var/log/syslog/var/log/messages
systemctl does not work in a container, on either distribution. A container runs one program, not a whole booted system, so there is no service manager listening. You will see Failed to get D-Bus connection. That is normal and it is not broken. In a container you start programs yourself, or you let the image's own start command do it. 在容器里,systemctl 两个发行版都用不了。 容器跑的是一个程序,不是一整套启动好的系统,所以没有服务管理器在听。 你会看到 Failed to get D-Bus connection。这是正常的,没坏。 在容器里你自己启动程序,或者交给镜像自带的启动命令。 systemctl ใช้ไม่ได้ในคอนเทนเนอร์ ทั้งสองดิสโทร คอนเทนเนอร์รันโปรแกรมเดียว ไม่ใช่ระบบที่บูตครบ จึงไม่มีตัวจัดการเซอร์วิสคอยฟังอยู่ คุณจะเห็นข้อความ Failed to get D-Bus connection นี่เป็นเรื่องปกติ ไม่ได้พัง ในคอนเทนเนอร์คุณเริ่มโปรแกรมเอง หรือให้คำสั่งเริ่มต้นของอิมเมจทำให้

⌨️ The command line

Summary

The shell reads one line and does one thing. It looks old, but it is faster than any window, and it is the only way into most servers. About twenty commands cover almost every day. Learn those twenty and four keys, and Linux stops being frightening.

摘要

Shell 读一行、做一件事。它看着很老,但比任何窗口都快,而且大多数服务器只能这样进去。大约二十条命令就能覆盖几乎每一天。学会这二十条加四个按键,Linux 就不吓人了。

สรุป

เชลล์อ่านหนึ่งบรรทัดแล้วทำหนึ่งอย่าง มันดูเก่า แต่เร็วกว่าหน้าต่างใด ๆ และเป็นทางเดียวที่เข้าเซิร์ฟเวอร์ส่วนใหญ่ได้ ประมาณยี่สิบคำสั่งครอบคลุมเกือบทุกวัน เรียนยี่สิบคำสั่งนี้กับปุ่มสี่ปุ่ม แล้ว Linux จะไม่น่ากลัวอีก

Read the prompt first

root@3f9c1a7b2e04:/home/ploy# ls │ │ │ └── # means you are root. $ means a normal user. │ │ └── where you are standing right now │ └── the computer's name (in a container, its id) └── who you are

In a container you are usually root already, so you almost never need sudo here. On a real server you are not root, and you put sudo in front of anything that changes the system. 在容器里你通常已经是 root,所以这里几乎不需要 sudo。 在真实服务器上你不是 root,凡是要改动系统的命令前面都要加 sudo ในคอนเทนเนอร์คุณมักเป็น root อยู่แล้ว จึงแทบไม่ต้องใช้ sudo ที่นี่ บนเซิร์ฟเวอร์จริงคุณไม่ใช่ root และต้องใส่ sudo ข้างหน้าทุกคำสั่งที่เปลี่ยนระบบ

The four keys that matter more than any command

  • Tab — finish my typing. Type three letters and press Tab. The shell completes the name. This also proves the file exists. Use it constantly. Tab —— 帮我补全。打三个字母按 Tab, shell 会把名字补完,同时也证明这个文件存在。要一直用它。 Tab — เติมให้จบ พิมพ์สามตัวอักษรแล้วกด Tab เชลล์จะเติมชื่อให้ครบ และยังพิสูจน์ว่าไฟล์นั้นมีอยู่จริง ใช้บ่อย ๆ
  • — the command I typed before. Never type a long command twice. —— 上一条命令。长命令不要打第二遍。 — คำสั่งที่พิมพ์ไปก่อนหน้า อย่าพิมพ์คำสั่งยาว ๆ ซ้ำสองครั้ง
  • Ctrl+C — stop this. Something is running and will not stop? This is the answer. Ctrl+C —— 停下。有东西在跑又停不下来?就按这个。 Ctrl+C — หยุดสิ่งนี้ มีอะไรกำลังทำงานและไม่ยอมหยุด? นี่คือคำตอบ
  • Ctrl+D — I am finished here. Inside a container this leaves it, the same as typing exit. Ctrl+D —— 我这儿完事了。 在容器里按它就是退出,跟输入 exit 一样。 Ctrl+D — ฉันเสร็จแล้ว ในคอนเทนเนอร์ การกดปุ่มนี้คือออกจากมัน เหมือนพิมพ์ exit

Command history — never type the same thing twice

The shell writes down every command you type. That list is the history, and it is numbered. Going back through it is the biggest speed-up available to you, and it is free. Shell 会把你输入的每一条命令记下来,这份清单就是历史记录,而且是带编号的。 回头去用它,是你能得到的最大提速,而且免费。 เชลล์จดทุกคำสั่งที่คุณพิมพ์ไว้ รายการนั้นเรียกว่า history และมีเลขกำกับ การย้อนกลับไปใช้มันคือการเร่งความเร็วที่ใหญ่ที่สุดที่คุณหาได้ และฟรี

history # the whole numbered list history 20 # just the last twenty history | grep docker # only the lines with docker in them

Now you can run any of them again by number. This is what the exclamation mark is for. The shell prints the command first, so you always see what you are about to run. 然后你就可以按编号再跑其中任何一条——感叹号就是干这个的。 Shell 会先把命令打出来,所以你总能看到自己要跑的是什么。 ทีนี้คุณรันอันไหนซ้ำก็ได้ด้วยเลขของมัน — เครื่องหมายตกใจมีไว้เพื่อสิ่งนี้ เชลล์จะพิมพ์คำสั่งออกมาก่อน คุณจึงเห็นเสมอว่ากำลังจะรันอะไร

$ history 1 apt-get update 2 apt-get install -y vim 3 ls -l /etc 4 cd /var/log 5 tail -n 20 syslog $ !2 apt-get install -y vim ← the shell shows it, then runs it
!42 # run command number 42 again !! # run the LAST command again !apt # run the last command that began with apt !$ # the last WORD of the previous command !* # all the arguments of the previous command
!$ is the quiet favourite. You type ls /var/log/nginx/error.log, it is there, and now you want to read it. Type less !$ and the shell fills in that long path for you. No copying, no retyping, no typo. !$ 是低调的最爱。你敲了 ls /var/log/nginx/error.log,文件在,现在你想读它—— 敲 less !$,shell 会把那条长路径替你填进去。不用复制、不用重打、不会打错。 !$ คือของโปรดแบบเงียบ ๆ คุณพิมพ์ ls /var/log/nginx/error.log ไฟล์อยู่จริง ตอนนี้คุณอยากอ่านมัน พิมพ์ less !$ แล้วเชลล์จะเติมพาธยาว ๆ นั้นให้เอง ไม่ต้องคัดลอก ไม่ต้องพิมพ์ใหม่ ไม่พิมพ์ผิด

Walking the stack: the arrows, and something better

  • and walk the history one step at a time. Press four times and you are four commands ago. Edit the line and press Enter to run the new version. The old one stays in the history untouched. 一步一步走历史记录。 按四下 就回到四条命令之前。改一改再按回车,就跑新的那条; 旧的那条还在历史里,没被动过。 และ เดินไปในประวัติทีละก้าว กด สี่ครั้ง คุณก็ย้อนไปสี่คำสั่งก่อนหน้า แก้บรรทัดแล้วกด Enter เพื่อรันเวอร์ชันใหม่ อันเก่ายังอยู่ในประวัติไม่ถูกแตะต้อง
  • Ctrl+R is far better than pressing thirty times. Press it, then type a few letters of the command you want. The shell searches backwards and shows the newest match as you type. Press Ctrl+R again to step to an older match. Enter runs it; or lets you edit it first; Ctrl+G gives up. Ctrl+R 比按三十下 好得多。 按下它,然后输入你想要的命令里的几个字母,shell 会往回搜,边打边显示最近的匹配。 再按一次 Ctrl+R 跳到更早的匹配。Enter 直接跑; 按 先改再跑;Ctrl+G 放弃。 Ctrl+R ดีกว่าการกด สามสิบครั้งมาก กดมัน แล้วพิมพ์ตัวอักษรสองสามตัวของคำสั่งที่ต้องการ เชลล์จะค้นย้อนหลังและแสดงผลที่ใหม่สุดขณะพิมพ์ กด Ctrl+R อีกครั้งเพื่อย้อนไปอันที่เก่ากว่า Enter รันเลย หรือ ให้แก้ก่อน Ctrl+G คือยกเลิก
  • Where the list is kept: ~/.bash_history, and it is written when the shell closes — not as you go. That is why a terminal you killed loses its history. 这份清单存在哪里:~/.bash_history, 而且是在 shell 关闭时才写进去,不是边打边写。所以被强制杀掉的终端会丢历史。 รายการเก็บไว้ที่ไหน: ~/.bash_history และเขียนตอนเชลล์ปิด ไม่ใช่เขียนไปเรื่อย ๆ จึงเป็นเหตุผลที่เทอร์มินัลที่ถูกฆ่าทิ้งจะเสียประวัติไป
Two things about history that catch people out.
1. In a container, the history dies with the container. Nothing is kept between runs unless you mount a folder.
2. !! and !42 only work in an interactive shell. Inside a script, or after bash -c, history expansion is switched off. This is the real error: 关于历史记录,有两点常让人踩坑:
1. 在容器里,历史记录跟容器一起消失——除非你挂载了文件夹,否则两次运行之间什么都不留。
2. !!!42 只在交互式 shell 里有效。 在脚本里,或者用 bash -c 之后,历史展开是关闭的。这是真实的报错:
สองเรื่องเกี่ยวกับ history ที่ทำให้คนสะดุด
1. ในคอนเทนเนอร์ ประวัติตายไปพร้อมคอนเทนเนอร์ ไม่มีอะไรถูกเก็บไว้ระหว่างการรัน เว้นแต่คุณเมาต์โฟลเดอร์ไว้
2. !! และ !42 ใช้ได้แค่ในเชลล์แบบอินเทอร์แอกทีฟ ในสคริปต์ หรือหลัง bash -c การขยาย history จะถูกปิด นี่คือข้อผิดพลาดจริง:
$ docker run --rm ubuntu:24.04 bash -c 'echo one; !!' one bash: line 1: !!: command not found
Your history remembers your secrets too. If you type a password or a key on the command line, it is now sitting in a plain text file. Two habits fix it: start the command with a space and it is not recorded at all, or clear the list afterwards. 历史记录也会记住你的秘密。如果你在命令行里敲了密码或密钥, 它现在就躺在一个纯文本文件里。两个习惯可以解决:命令前面加一个空格,就完全不记录; 或者事后把清单清掉。 ประวัติของคุณจำความลับไว้ด้วย ถ้าคุณพิมพ์รหัสผ่านหรือคีย์ลงบรรทัดคำสั่ง มันจะไปนอนอยู่ในไฟล์ข้อความธรรมดา สองนิสัยนี้แก้ได้: เริ่มคำสั่งด้วยช่องว่าง แล้วมันจะไม่ถูกบันทึกเลย หรือล้างรายการทีหลัง
export HISTCONTROL=ignorespace # a leading space = not recorded mysql -u root -psecret123 # ← note the space before mysql history -c # forget this session's list

sudo — borrowing root for one command

On a real server you do not work as root. You work as yourself and put sudo in front of the commands that change the machine. It asks for your password, does one thing with full power, and hands the power straight back. 在真实服务器上你用 root 干活。你用自己的身份, 在要改动机器的命令前面加 sudo。它会问你自己的密码, 用完整权限做一件事,然后立刻把权限交回去。 บนเซิร์ฟเวอร์จริง คุณไม่ทำงานเป็น root คุณทำงานเป็นตัวคุณเอง และใส่ sudo ข้างหน้าคำสั่งที่เปลี่ยนเครื่อง มันจะถามรหัสผ่านของคุณ ทำหนึ่งอย่างด้วยอำนาจเต็ม แล้วคืนอำนาจทันที

sudo apt-get install -y vim # one command as root sudo -l # what am I allowed to do? sudo -u ploy whoami # run it as a different user sudo -i # become root for a whole session. exit to leave.
sudo !! — the most loved two-word command in Linux. You type a command, you forget sudo, and it says Permission denied. Do not retype it. sudo !! takes the last command out of the history and runs it again with root. This is history and sudo working together, and you will use it for the rest of your life. sudo !! —— Linux 里最受喜爱的两个词。 你敲了一条命令,忘了加 sudo,它回你 Permission denied。 别重打——sudo !! 会把历史里的上一条命令取出来,用 root 再跑一遍。 这是历史记录和 sudo 联手,你会用它一辈子。 sudo !! — คำสั่งสองคำที่คนรักที่สุดใน Linux คุณพิมพ์คำสั่ง ลืมใส่ sudo แล้วมันตอบว่า Permission denied อย่าพิมพ์ใหม่ sudo !! จะดึงคำสั่งล่าสุดจากประวัติมารันซ้ำด้วยสิทธิ์ root นี่คือ history กับ sudo ทำงานร่วมกัน และคุณจะใช้มันไปตลอดชีวิต
$ apt-get install -y vim E: Could not open lock file /var/lib/dpkg/lock-frontend - Permission denied $ sudo !! sudo apt-get install -y vim ← it rebuilds the line for you, then runs it

Who is allowed to use sudo? The families disagree

You are allowed to use sudo because you are in a special group — and the two families named that group differently. This is a real difference that costs people time, and it is easy to check. 你能用 sudo,是因为你在一个特殊的组里—— 而两大家族给这个组起了不同的名字。这是一个会浪费时间的真实差别,好在很容易查。 คุณใช้ sudo ได้เพราะคุณอยู่ในกลุ่มพิเศษ — และสองตระกูลตั้งชื่อกลุ่มนั้นไม่เหมือนกัน นี่คือความต่างจริงที่ทำให้คนเสียเวลา และตรวจง่าย

# Ubuntu — the group is called sudo root@ubuntu:/# getent group sudo sudo:x:27:ubuntu root@ubuntu:/# getent group wheel ← nothing. there is no wheel group. # Rocky Linux — the group is called wheel [root@rocky /]# getent group wheel wheel:x:10: [root@rocky /]# getent group sudo ← nothing. there is no sudo group.
# give a user sudo power — Ubuntu usermod -aG sudo ploy # give a user sudo power — Rocky Linux usermod -aG wheel ploy
sudo is not installed in any of these container images. Not in ubuntu:24.04 and not in rockylinux:9. You will see bash: sudo: command not found, and that is correct — you are already root in a container, so there is nothing to borrow. Install it only if you want to practise being a normal user. 这三个容器镜像里都没有装 sudo ubuntu:24.04 没有,rockylinux:9 也没有。 你会看到 bash: sudo: command not found——这是对的: 在容器里你本来就是 root,没什么可借的。只有当你想练习“当普通用户”时才需要装它。 sudo ไม่ได้ติดตั้งในอิมเมจคอนเทนเนอร์เหล่านี้เลย ไม่มีใน ubuntu:24.04 และไม่มีใน rockylinux:9 คุณจะเห็น bash: sudo: command not found ซึ่งถูกต้อง — คุณเป็น root อยู่แล้วในคอนเทนเนอร์ จึงไม่มีอะไรต้องยืม ติดตั้งมันเฉพาะเมื่อคุณอยากฝึกเป็นผู้ใช้ธรรมดา
# practise as a normal user, on purpose apt-get update && apt-get install -y sudo # Ubuntu dnf install -y sudo # Rocky Linux useradd -m ploy usermod -aG sudo ploy # wheel on the Red Hat side su - ploy # become ploy. now the prompt ends in $ not #
Never edit /etc/sudoers with a normal editor. Use visudo. It opens the same file, but it checks the file makes sense before it saves. One bad line in that file and nobody can use sudo any more, including you — on a remote server that means you are locked out for good. 绝不要用普通编辑器改 /etc/sudoersvisudo:它打开的是同一个文件,但会在保存前检查语法。 那个文件里写错一行,所有人都用不了 sudo,包括你—— 在远程服务器上,这意味着你被永久锁在外面。 อย่าแก้ /etc/sudoers ด้วยเอดิเตอร์ธรรมดาเด็ดขาด ให้ใช้ visudo มันเปิดไฟล์เดียวกัน แต่ตรวจว่าไฟล์ถูกต้องก่อนบันทึก ผิดบรรทัดเดียวในไฟล์นั้น ไม่มีใครใช้ sudo ได้อีก รวมทั้งคุณเอง — บนเซิร์ฟเวอร์ระยะไกลนั่นหมายถึงคุณถูกล็อกออกอย่างถาวร

Where am I, and what is here?

pwd # print working directory — where am I? ls # what is here ls -l # the long list: permissions, owner, size, date ls -la # and the hidden files (their names start with a dot) cd /etc # go there cd .. # go up one cd ~ # go home cd - # go back to where I just was

Look inside a file

cat /etc/hostname # small file: throw it all on the screen less /etc/services # big file: page through it. q to quit. head -n 5 /etc/passwd # the first five lines tail -n 5 /etc/passwd # the last five lines tail -f /var/log/syslog # follow: keep showing new lines. Ctrl+C to stop. wc -l /etc/passwd # how many lines?
tail -f is how the job is really done. When something is broken on a server, you open the log with tail -f in one window, then make the problem happen in another window, and watch the error arrive. Learn this one properly. tail -f 才是真正干活的方式。服务器出问题时, 在一个窗口用 tail -f 打开日志,在另一个窗口把问题重现一遍, 看着错误自己出现。这一条要好好学。 tail -f คือวิธีทำงานจริง เวลามีอะไรพังบนเซิร์ฟเวอร์ ให้เปิดล็อกด้วย tail -f ในหน้าต่างหนึ่ง แล้วไปทำให้ปัญหาเกิดในอีกหน้าต่าง และดูข้อผิดพลาดโผล่มา เรียนอันนี้ให้ดี

Make, copy, move, delete

mkdir notes # make a folder mkdir -p a/b/c # make the whole chain, no complaining touch notes/day1.txt # make an empty file cp day1.txt day2.txt # copy cp -r notes backup # copy a folder and everything in it mv day2.txt notes/ # move — and also how you rename rm day1.txt # delete a file rm -r backup # delete a folder and everything in it
There is no recycle bin. rm is final. There is no "are you sure?", no undo, and no undelete. Before any rm -r, run the same path with ls first and read what it prints. This one habit will save you one very bad afternoon. And never type rm -rf / — it means "delete everything". 没有回收站。rm 是不可逆的:不会问“你确定吗”, 没有撤销,也没有恢复。每次要 rm -r 之前,先用 ls 跑一遍同样的路径,把它列出来的东西读一遍。这个习惯能救你一个很糟糕的下午。 永远不要输入 rm -rf /——那是“删掉一切”。 ไม่มีถังขยะ rm คือจบเลย ไม่มีคำถามว่า "แน่ใจไหม" ไม่มีการย้อนกลับ และกู้คืนไม่ได้ ก่อนใช้ rm -r ทุกครั้ง ให้รัน ls กับพาธเดียวกันก่อน แล้วอ่านสิ่งที่มันแสดง นิสัยข้อเดียวนี้จะช่วยคุณจากบ่ายวันที่แย่มากหนึ่งวัน และอย่าพิมพ์ rm -rf / เด็ดขาด — มันหมายถึง "ลบทุกอย่าง"

Find things

grep "root" /etc/passwd # which lines contain this word? grep -r "password" /etc # search every file under a folder grep -i "error" app.log # -i ignores capital letters find / -name "*.conf" # find files by name find /var -size +10M # find files bigger than 10 megabytes which vim # where is this program?

Join commands together

This is the idea that makes Linux powerful. Each command is small. The pipe | sends the answer of one into the next. 这就是让 Linux 强大的那个想法:每条命令都很小, 管道 | 把一条命令的答案送进下一条。 นี่คือแนวคิดที่ทำให้ Linux ทรงพลัง แต่ละคำสั่งเล็ก ๆ ไปป์ | ส่งคำตอบของคำสั่งหนึ่งเข้าไปในคำสั่งถัดไป

ls -l | grep ".txt" # list files, keep only the txt lines cat /etc/passwd | wc -l # how many users are there? ps aux | grep nginx # is nginx running? dpkg -l | grep vim # is vim installed? (Ubuntu) echo "hello" > note.txt # > writes, and DESTROYS what was there echo "again" >> note.txt # >> adds to the end. Much safer.
One > or two changes everything. One > empties the file first. Two >> add to the bottom. People overwrite a good file with one arrow and no warning at all. 一个 > 还是两个,结果完全不同。 一个 > 会先把文件清空;两个 >> 追加到末尾。 很多人就是用一个箭头,把一个好文件无声无息地覆盖掉了。 > อันเดียวหรือสองอัน เปลี่ยนทุกอย่าง > อันเดียวจะล้างไฟล์ก่อน สองอัน >> จะต่อท้าย หลายคนเขียนทับไฟล์ดี ๆ ด้วยลูกศรอันเดียว โดยไม่มีคำเตือนอะไรเลย

Permissions — reading drwxr-xr-x

$ ls -l -rw-r--r-- 1 root root 142 Aug 24 09:14 note.txt drwxr-xr-x 2 ploy ploy 4096 Aug 24 09:15 notes -rwxr-xr-x 1 root root 1183 Aug 24 09:16 run.sh d rwx r-x r-x │ │ │ └── everyone else may: read, and run it │ │ └────── the group may: read, and run it │ └────────── the owner may: read, write, and run it └───────────── d = a directory. - = a normal file. l = a shortcut.
chmod +x run.sh # make it runnable chmod 644 note.txt # owner read+write, everyone else read chmod 600 secret.txt # only the owner may even read it chown ploy:ploy note.txt # give it to another user
Why the numbers? read is 4, write is 2, run is 1. Add them up for each of the three groups. So 6 is read plus write, and 7 is all three. 644 means owner 6, group 4, everyone 4. That is the most common number you will ever type. 那些数字是怎么来的?读是 4,写是 2,执行是 1, 三组各自相加。所以 6 是读加写,7 是三样全有。 644 就是拥有者 6、组 4、其他人 4——这是你最常敲的一个数字。 ทำไมเป็นตัวเลข? อ่านคือ 4 เขียนคือ 2 รันคือ 1 บวกกันในแต่ละกลุ่มทั้งสามกลุ่ม ดังนั้น 6 คืออ่านบวกเขียน และ 7 คือครบทั้งสาม 644 คือเจ้าของ 6 กลุ่ม 4 คนอื่น 4 นี่คือตัวเลขที่คุณจะพิมพ์บ่อยที่สุด

Who and what is on this machine?

whoami # which user am I? id # my user number and my groups ps aux # every running program top # live: what is using the CPU. q to quit. df -h # how full is the disk? -h means human sizes free -h # how much memory is left uname -a # which kernel is this

💾 Storage — keeping what matters

Summary

A container forgets everything when you delete it. That is on purpose, and it is good. But your database and your work must not be forgotten. There are two ways to keep files: a volume, which Docker looks after, and a bind mount, which is a folder from your own computer. Learn when to use each.

摘要

容器被删掉时会忘掉一切。这是故意设计的,而且是好事。但你的数据库和你的工作不能被忘掉。保住文件有两种办法:数据卷(Docker 替你保管)和绑定挂载(就是你自己电脑上的一个文件夹)。要学会什么时候用哪个。

สรุป

คอนเทนเนอร์ลืมทุกอย่างเมื่อคุณลบมัน นั่นเป็นความตั้งใจ และเป็นเรื่องดี แต่ฐานข้อมูลและงานของคุณต้องไม่ถูกลืม มีสองวิธีเก็บไฟล์: วอลลุมที่ Docker ดูแลให้ และไบนด์เมาต์ซึ่งคือโฟลเดอร์จากเครื่องของคุณเอง เรียนรู้ว่าเมื่อไรควรใช้อันไหน

📦 A volume

Docker makes it and looks after it. You never need to know where it is on the disk. Best for data a program owns: databases, uploads, caches. Docker 建它、管它,你不用知道它在硬盘哪里。 最适合程序自己拥有的数据:数据库、上传文件、缓存。 Docker สร้างและดูแลให้ คุณไม่ต้องรู้ว่ามันอยู่ที่ไหนบนดิสก์ เหมาะที่สุดกับข้อมูลที่โปรแกรมเป็นเจ้าของ: ฐานข้อมูล ไฟล์อัปโหลด แคช

-v labdata:/data

📂 A bind mount

A real folder from your computer, appearing inside the container. Best for files you edit: your code, a settings file. Edit on your computer, the container sees it at once. 你电脑上一个真实的文件夹,出现在容器里面。 最适合你要编辑的文件:你的代码、配置文件。你在电脑上改,容器立刻就看到。 โฟลเดอร์จริงจากเครื่องคุณ ที่ไปปรากฏอยู่ข้างในคอนเทนเนอร์ เหมาะที่สุดกับไฟล์ที่คุณแก้: โค้ดของคุณ ไฟล์ตั้งค่า แก้บนเครื่องคุณ คอนเทนเนอร์เห็นทันที

-v "$PWD":/site

1 Prove it to yourself

Run these four commands in order. They are exactly the commands used to test this section, and the output below is the real output. 按顺序跑这四条命令。它们就是用来验证这一节的那几条,下面的输出是真实输出。 รันสี่คำสั่งนี้ตามลำดับ นี่คือคำสั่งที่ใช้ทดสอบหัวข้อนี้จริง ๆ และผลลัพธ์ข้างล่างคือผลลัพธ์จริง

docker volume create labdata # write one file in the volume, and one inside the container itself docker run --rm -v labdata:/data ubuntu:24.04 sh -c \ 'echo "written to the volume" > /data/note.txt; \ echo "written to the container" > /root/note.txt' # brand new container, same volume — which file is still there? docker run --rm -v labdata:/data ubuntu:24.04 sh -c \ 'cat /data/note.txt; cat /root/note.txt'
written to the volume cat: /root/note.txt: No such file or directory

The volume kept the file. The container's own file died with the container. That is the whole lesson, and it cost somebody a database to learn. 数据卷把文件留住了;容器自己的文件跟着容器一起死了。 这就是全部要点——曾经有人是丢了一个数据库才学到的。 วอลลุมเก็บไฟล์ไว้ได้ ไฟล์ในตัวคอนเทนเนอร์เองตายไปพร้อมคอนเทนเนอร์ นี่คือบทเรียนทั้งหมด และมีคนต้องเสียฐานข้อมูลไปหนึ่งก้อนเพื่อเรียนรู้มัน

2 Looking after volumes

docker volume ls # what volumes do I have? docker volume inspect labdata # where does it really live? docker volume rm labdata # delete it — this IS final docker volume prune # delete every volume nothing is using
docker volume prune is the dangerous one. It deletes every volume that no container is using right now. A stopped project's database counts as "not in use". Read the list it offers you before you say yes. docker volume prune 是危险的那个。 它会删掉此刻没有容器在用的每一个数据卷——一个停掉的项目的数据库也算“没在用”。 在你点“是”之前,先把它列出来的清单读一遍。 docker volume prune คืออันที่อันตราย มันลบทุกวอลลุมที่ไม่มีคอนเทนเนอร์ใช้อยู่ในขณะนั้น ฐานข้อมูลของโปรเจกต์ที่หยุดอยู่ก็นับว่า "ไม่ได้ใช้" อ่านรายการที่มันเสนอให้ก่อนตอบตกลง

3 Bind mounts, and the two computers

The syntax for "this folder, here" is different on the two systems, because the way they write a folder name is different. “这个文件夹,放到这里”的写法在两种系统上不同,因为它们写文件夹名字的方式不同。 ไวยากรณ์ของ "โฟลเดอร์นี้ ไปไว้ที่นี่" ต่างกันในสองระบบ เพราะวิธีเขียนชื่อโฟลเดอร์ต่างกัน

# Mac / Linux docker run -it -v "$PWD":/site -w /site ubuntu:24.04 bash # Windows PowerShell docker run -it -v "${PWD}:/site" -w /site ubuntu:24.04 bash
A bind mount hides what was already there. If you mount a folder onto /etc, the container's real /etc is not deleted — it is covered up, like putting a rug over a floor. Take the mount away and the floor is still there. Mounting over a folder the program needs is a very confusing way to break it. 绑定挂载会盖住原来的东西。如果你把一个文件夹挂到 /etc 上, 容器里真正的 /etc 并没有被删掉——只是被盖住了,像地板上铺了块地毯。 把挂载去掉,地板还在。挂到程序需要的目录上,是一种非常令人困惑的弄坏方式。 ไบนด์เมาต์ปิดทับสิ่งที่มีอยู่เดิม ถ้าคุณเมาต์โฟลเดอร์ทับ /etc ตัว /etc จริงในคอนเทนเนอร์ไม่ได้ถูกลบ แต่ถูกปิดทับ เหมือนปูพรมทับพื้น เอาเมาต์ออกแล้วพื้นก็ยังอยู่ การเมาต์ทับโฟลเดอร์ที่โปรแกรมต้องใช้เป็นวิธีทำให้พังที่ทำให้สับสนมาก

4 Mount any folder you like — including an external drive

$PWD is only a shortcut for "the folder I am standing in". You can name any folder on your computer instead, and that is how you reach a big external disk, a photo library, or a project that lives somewhere else. $PWD 只是“我现在所在的文件夹”的简写。 你可以换成你电脑上任何一个文件夹——外接大硬盘、照片库,或者放在别处的项目,就是这样接进来的。 $PWD เป็นแค่ทางย่อของ "โฟลเดอร์ที่ฉันยืนอยู่" คุณใส่ชื่อโฟลเดอร์ไหนบนเครื่องก็ได้ และนั่นคือวิธีเข้าถึงฮาร์ดดิสก์ภายนอกตัวใหญ่ คลังรูปภาพ หรือโปรเจกต์ที่อยู่ที่อื่น

# Windows — a normal folder, and an external drive as D: docker run -it -v "C:/Users/Admin/projects:/site" ubuntu:24.04 bash docker run -it -v "D:/photos:/photos" ubuntu:24.04 bash # Mac / Linux — an external drive lives under /Volumes or /media docker run -it -v "/Users/admin/projects:/site" ubuntu:24.04 bash docker run -it -v "/Volumes/BACKUP/photos:/photos" ubuntu:24.04 bash
Windows: use forward slashes. Write C:/Users/Admin/projects, not C:\Users\Admin\projects. Docker understands the forward-slash form on every system, and the backslash form causes strange errors that do not name the real problem. If the path has a space in it, keep the whole thing in quotes. Windows:请用正斜杠。C:/Users/Admin/projects, 不要写 C:\Users\Admin\projects。正斜杠形式在所有系统上 Docker 都认, 反斜杠会引出一些看不出真正原因的怪错误。路径里有空格时,整个用引号括起来。 Windows: ให้ใช้ขีดทับไปหน้า เขียน C:/Users/Admin/projects ไม่ใช่ C:\Users\Admin\projects Docker เข้าใจแบบขีดทับไปหน้าในทุกระบบ ส่วนแบบขีดทับกลับหลังทำให้เกิดข้อผิดพลาดแปลก ๆ ที่ไม่บอกสาเหตุจริง ถ้าพาธมีช่องว่าง ให้ครอบทั้งหมดด้วยเครื่องหมายคำพูด

Read-only mounts — the habit that saves your files

Add :ro at the end and the container can look but not touch. Use this for anything you would be sad to lose: your photos, your only copy of something, a folder you are only letting a program read. 在末尾加 :ro,容器就只能看不能动。 任何你丢了会心疼的东西都该这样挂:你的照片、某个唯一的副本、 或者只是让程序读一读的文件夹。 เติม :ro ท้ายสุด แล้วคอนเทนเนอร์จะดูได้แต่แตะไม่ได้ ใช้กับอะไรที่คุณจะเสียใจถ้าหาย: รูปถ่ายของคุณ สำเนาชิ้นเดียวที่มี หรือโฟลเดอร์ที่คุณให้โปรแกรมอ่านอย่างเดียว

docker run -it -v "C:/Users/Admin/photos:/photos:ro" ubuntu:24.04 bash
root@69ebbae57949:/# cat /mnt/host/host_note.txt hello from the host ← reading is fine root@69ebbae57949:/# echo x > /mnt/host/nope.txt bash: /mnt/host/nope.txt: Read-only file system ← writing is refused

Mount one single file

You do not have to share a whole folder. One file works, which is the neat way to drop a settings file into a container without rebuilding the image. 不一定要共享整个文件夹,一个文件也行—— 这是把配置文件塞进容器而不用重建镜像的漂亮做法。 คุณไม่จำเป็นต้องแชร์ทั้งโฟลเดอร์ ไฟล์เดียวก็ได้ ซึ่งเป็นวิธีสวย ๆ ในการยัดไฟล์ตั้งค่าเข้าไปในคอนเทนเนอร์โดยไม่ต้องสร้างอิมเมจใหม่

docker run -d -v "C:/Users/Admin/nginx.conf:/etc/nginx/nginx.conf:ro" nginx:alpine

The longer, clearer way to write it

Real projects often use --mount instead of -v. It is more typing, but every part is named, so you can read it a year later and still know what it does. It also fails loudly if the folder does not exist, while -v silently creates an empty one — which is the cause of a lot of "where did my files go?". 真实项目里常用 --mount 代替 -v。 字打得多一点,但每一部分都有名字,一年后再看也知道它在干什么。 而且如果文件夹不存在,它会明确报错;-v 会悄悄建一个空的—— 这正是很多“我的文件哪去了?”的来源。 โปรเจกต์จริงมักใช้ --mount แทน -v พิมพ์มากกว่า แต่ทุกส่วนมีชื่อกำกับ อ่านอีกทีในอีกหนึ่งปีก็ยังรู้ว่ามันทำอะไร และมันจะแจ้งข้อผิดพลาดชัดเจนถ้าโฟลเดอร์ไม่มีอยู่ ขณะที่ -v จะสร้างโฟลเดอร์เปล่าให้เงียบ ๆ ซึ่งเป็นต้นเหตุของคำถาม "ไฟล์ฉันหายไปไหน" จำนวนมาก

docker run -it \ --mount type=bind,source=C:/Users/Admin/projects,target=/site,readonly \ --mount type=volume,source=labdata,target=/data \ ubuntu:24.04 bash
If the folder does not appear, Docker Desktop has not been given permission. On Windows and Mac, Docker can only share folders you have allowed. Open Settings → Resources → File sharing and add the folder — an external drive almost always needs this. The symptom is an empty folder inside the container with no error at all, which is why people hunt in the wrong place for an hour. 如果文件夹没出现,是 Docker Desktop 没被授权。 在 Windows 和 Mac 上,Docker 只能共享你允许过的文件夹。打开 Settings → Resources → File sharing 把文件夹加进去——外接硬盘几乎一定要这一步。 症状是容器里那个文件夹是空的,而且完全没有报错,所以大家会往错的方向找一个小时。 ถ้าโฟลเดอร์ไม่ปรากฏ แปลว่า Docker Desktop ยังไม่ได้รับอนุญาต บน Windows และ Mac Docker แชร์ได้เฉพาะโฟลเดอร์ที่คุณอนุญาตไว้ เปิด Settings → Resources → File sharing แล้วเพิ่มโฟลเดอร์นั้น — ฮาร์ดดิสก์ภายนอกต้องทำแทบทุกครั้ง อาการคือโฟลเดอร์ในคอนเทนเนอร์ว่างเปล่าและไม่มีข้อผิดพลาดใด ๆ จึงเป็นเหตุผลที่คนหาผิดที่อยู่เป็นชั่วโมง

5 Copy files in and out without any mount

docker cp note.txt ubu:/root/ # from your computer into the container docker cp ubu:/root/note.txt . # and back out again

Useful when you only need one file and cannot be bothered with a volume. 只需要传一个文件、又懒得弄数据卷的时候很好用。 มีประโยชน์เวลาคุณต้องการแค่ไฟล์เดียวและไม่อยากยุ่งกับวอลลุม

🌐 Networking — making containers talk

Summary

Every container gets its own address, and that address changes. So you never use addresses. You make your own network and then containers can find each other by name. On the network Docker gives you by default, names do not work, and that costs beginners a whole evening. One extra command fixes it for ever.

摘要

每个容器都有自己的地址,而地址会变,所以你永远不用地址。你自己建一个网络,容器就能靠名字找到彼此。在 Docker 默认给你的网络里,名字不管用——这会让新手浪费一整个晚上。多敲一条命令就永久解决了。

สรุป

คอนเทนเนอร์แต่ละตัวมีที่อยู่ของตัวเอง และที่อยู่นั้นเปลี่ยนได้ คุณจึงไม่ใช้ที่อยู่เลย ให้สร้างเน็ตเวิร์กของคุณเอง แล้วคอนเทนเนอร์จะหากันด้วยชื่อได้ บนเน็ตเวิร์กที่ Docker ให้มาโดยปริยาย ชื่อใช้ไม่ได้ และเรื่องนี้ทำให้มือใหม่เสียเวลาไปทั้งเย็น คำสั่งเพิ่มอันเดียวแก้ได้ตลอดไป

1 The one command that changes everything

docker network create labnet

Now put every container that must talk to the others on labnet. 现在把所有需要互相通话的容器都放到 labnet 上。 ตอนนี้เอาทุกคอนเทนเนอร์ที่ต้องคุยกันไปไว้บน labnet

docker run -d --name web --network labnet nginx:alpine docker run -it --name app --network labnet ubuntu:24.04 bash

2 Names work — this is the real test output

Inside app, ask for web by name. On labnet it answers. On the default network it does not. Both of these were run for real: app 里面用名字找 web。 在 labnet 上它会回答,在默认网络上不会。下面两个都是真的跑过的: ข้างใน app ให้ถามหา web ด้วยชื่อ บน labnet มันตอบ บนเน็ตเวิร์กปริยายมันไม่ตอบ ทั้งสองอันนี้รันจริงมาแล้ว

# both containers on labnet root@app:/# getent hosts web 172.20.0.2 web ← it works # the same question on the default bridge network root@other:/# getent hosts web ← nothing. no answer at all.

Once names work, you can talk to it properly. web here is a real web server, so ask it for a page: 名字能用之后就可以正常通话了。这里的 web 是一个真的网页服务器, 去要一个页面看看: พอชื่อใช้ได้แล้ว คุณก็คุยกับมันได้จริง ๆ web ที่นี่คือเว็บเซิร์ฟเวอร์จริง ลองขอหน้าเว็บจากมันดู

apt-get update && apt-get install -y curl iputils-ping ping -c 2 web curl http://web # the nginx welcome page comes back

3 What is my own address?

Remember that ip is not installed in the Ubuntu image. There is a way that always works, in every image, with nothing installed: 记住 Ubuntu 镜像里没装 ip。有一个办法在任何镜像里、 什么都不用装就永远好用: จำไว้ว่าอิมเมจ Ubuntu ไม่ได้ติดตั้ง ip มีวิธีที่ใช้ได้เสมอในทุกอิมเมจ โดยไม่ต้องติดตั้งอะไรเลย

hostname -i 172.20.0.3
Do not write that number anywhere. Delete the container, start it again, and the number is different. Names last; numbers do not. This is the same lesson as the Kubernetes Service. 不要把那个数字写到任何地方。删掉容器再启动,数字就变了。 名字长久,数字不长久。这和 Kubernetes 里 Service 的那一课是同一件事。 อย่าจดตัวเลขนั้นไว้ที่ไหน ลบคอนเทนเนอร์แล้วเปิดใหม่ ตัวเลขก็เปลี่ยน ชื่ออยู่ทน ตัวเลขไม่ทน นี่คือบทเรียนเดียวกับ Service ใน Kubernetes

4 Two words that confuse everybody

  • localhost inside a container means the container itself — not your computer. A program looking for a database on localhost inside a container will never find one running next door. Use the other container's name. 容器里的 localhost 指的是这个容器本身, 不是你的电脑。容器里的程序去 localhost 找数据库, 永远找不到隔壁那个。要用另一个容器的名字 localhost ข้างในคอนเทนเนอร์หมายถึงตัวคอนเทนเนอร์เอง ไม่ใช่เครื่องคุณ โปรแกรมที่หาฐานข้อมูลที่ localhost ข้างในคอนเทนเนอร์ จะไม่เจอตัวที่รันอยู่ข้าง ๆ เลย ให้ใช้ชื่อของคอนเทนเนอร์อีกตัว
  • To reach your own computer from inside a container, use host.docker.internal. It works on Docker Desktop for Windows and Mac. 要从容器里访问你自己的电脑,用 host.docker.internal。在 Windows 和 Mac 的 Docker Desktop 上有效。 ถ้าจะเข้าถึงเครื่องของคุณเองจากในคอนเทนเนอร์ ให้ใช้ host.docker.internal ใช้ได้บน Docker Desktop ทั้ง Windows และ Mac

5 Two different doors

What you wantWhat you writeWho can reach it
Container to container--network labnet, then use the nameonly containers on that network
You, in your browser-p 8080:80anything on your computer

-p is only for you and your browser. Containers talking to each other do not need -p at all — a very common piece of extra typing. -p 只是为了你和你的浏览器。容器之间互相通话完全不需要 -p——这是很常见的多余输入。 -p มีไว้สำหรับคุณกับเบราว์เซอร์ของคุณเท่านั้น คอนเทนเนอร์ที่คุยกันเองไม่ต้องใช้ -p เลย นี่คือการพิมพ์เกินที่พบบ่อยมาก

docker network ls # my networks docker network inspect labnet # who is on labnet, and their addresses docker network connect labnet ubu # add a running container to a network docker network rm labnet # delete it (nothing may be using it)

📝 vim — and how to get out of it

Summary

One day you will open vim by accident and not be able to leave. Everybody has done it. vim is strange for one reason only: the keys do different things at different times. That is called a mode. Learn the modes and vim becomes the fastest editor you have. Practise on the real editor below, in this page.

摘要

总有一天你会不小心打开 vim,然后出不来。每个人都经历过。vim 之所以奇怪只有一个原因:同一个按键在不同时候做不同的事,这叫“模式”。把模式学会,vim 就是你手上最快的编辑器。就在下面这个真的编辑器里练。

สรุป

วันหนึ่งคุณจะเปิด vim โดยไม่ได้ตั้งใจ แล้วออกไม่ได้ ทุกคนเคยเจอ vim แปลกเพราะเหตุผลเดียว คือปุ่มเดียวกันทำงานต่างกันในเวลาต่างกัน สิ่งนั้นเรียกว่าโหมด เรียนโหมดให้เข้าใจ แล้ว vim จะเป็นเอดิเตอร์ที่เร็วที่สุดที่คุณมี ฝึกกับเอดิเตอร์จริงข้างล่างในหน้านี้ได้เลย

The panic exit, before anything else. Press Esc, then type :q! and press Enter. That leaves and throws away your changes. It always works. Memorise it now and the fear is gone. 先给你逃生出口:Esc,输入 :q!,按 Enter。这会退出并丢掉你的改动。它永远有效。现在就背下来,恐惧就没了。 ทางออกฉุกเฉิน ก่อนอย่างอื่นทั้งหมด กด Esc แล้วพิมพ์ :q! และกด Enter นั่นคือออกและทิ้งการแก้ไขทั้งหมด ใช้ได้เสมอ จำไว้เดี๋ยวนี้ แล้วความกลัวก็หมดไป

Why learn it at all?

Because vim is on every Linux server, always, with nothing installed. One day you will be fixing a broken machine over a slow connection with no graphical screen, and vim will be the only editor there. That day, ten minutes of practice today is worth a great deal. 因为 vim 在每一台 Linux 服务器上都有,永远都有,什么都不用装。 总有一天你会在一条很慢的连接上修一台坏机器、没有图形界面,而 vim 是那里唯一的编辑器。 到那天,今天这十分钟的练习会非常值钱。 เพราะ vim มีอยู่บนเซิร์ฟเวอร์ Linux ทุกเครื่อง เสมอ โดยไม่ต้องติดตั้งอะไร วันหนึ่งคุณจะต้องซ่อมเครื่องที่พังผ่านการเชื่อมต่อที่ช้าและไม่มีหน้าจอกราฟิก และ vim จะเป็นเอดิเตอร์เดียวที่นั่น วันนั้น การฝึกสิบนาทีในวันนี้จะมีค่ามาก

Except in a fresh Ubuntu container. Remember section 3 — the official Ubuntu image has no vim and not even vi. On a real Ubuntu server vim is there. In the container you install it first. 但在新的 Ubuntu 容器里例外。还记得第 3 节吗—— 官方 Ubuntu 镜像里没有 vim,连 vi 都没有。真实的 Ubuntu 服务器上是有的; 在容器里你得先装。 ยกเว้นในคอนเทนเนอร์ Ubuntu ใหม่ จำหัวข้อ 3 ได้ไหม — อิมเมจ Ubuntu ทางการไม่มี vim และไม่มีแม้แต่ vi บนเซิร์ฟเวอร์ Ubuntu จริงมี vim อยู่ ในคอนเทนเนอร์คุณต้องติดตั้งก่อน

The one idea: three modes

  • NORMAL — where you start. Keys are commands, not letters. Pressing d deletes; it does not type a d. Press Esc to come back here from anywhere. NORMAL(普通模式)—— 你一进来就在这里。按键是命令,不是字母: 按 d 是删除,不会打出一个 d。在任何地方按 Esc 都能回到这里。 NORMAL — จุดที่คุณเริ่ม ปุ่มคือคำสั่ง ไม่ใช่ตัวอักษร กด d คือลบ ไม่ได้พิมพ์ตัว d กด Esc เพื่อกลับมาที่นี่จากที่ไหนก็ได้
  • INSERT — press i to get here. Now keys are letters, like every other editor you have used. This is where you actually write. INSERT(插入模式)—— 按 i 进来。 现在按键就是字母,跟你用过的其他编辑器一样。你在这里写字。 INSERT — กด i เพื่อเข้ามา ตอนนี้ปุ่มคือตัวอักษร เหมือนเอดิเตอร์อื่น ๆ ที่คุณใช้ นี่คือที่ที่คุณเขียนจริง
  • COMMAND — press : to get here. A line opens at the bottom and you type a whole word: w to write, q to quit. COMMAND(命令模式)—— 按 : 进来。 底部会出现一行,你在那里输入完整的词:w 是保存,q 是退出。 COMMAND — กด : เพื่อเข้ามา จะมีบรรทัดเปิดขึ้นด้านล่าง แล้วคุณพิมพ์เป็นคำ: w คือบันทึก q คือออก
Almost every vim problem is one thing: you are in the wrong mode. Text appearing in strange places, letters disappearing, nothing happening at all — press Esc and start again from NORMAL. When lost, press Esc. 几乎所有 vim 问题都是同一件事:你在错误的模式里。 文字出现在奇怪的地方、字母消失、或者完全没反应——按 Esc,从 NORMAL 重新开始。 迷路了就按 Esc。 ปัญหา vim เกือบทุกอย่างคือเรื่องเดียว คุณอยู่ผิดโหมด ตัวหนังสือโผล่ในที่แปลก ๆ ตัวอักษรหายไป หรือไม่มีอะไรเกิดขึ้นเลย — กด Esc แล้วเริ่มใหม่จาก NORMAL ถ้าหลงทาง กด Esc

Practise here — this is a working vim

Click the black box, then type. It behaves like the real thing: the keys do nothing until you understand the mode you are in. Nothing here can break anything. 点一下那个黑框,然后开始打字。它的行为和真的一样: 在你搞清楚自己在哪个模式之前,按键什么也不会做。这里怎么弄都弄不坏东西。 คลิกกล่องสีดำ แล้วพิมพ์ มันทำงานเหมือนของจริง ปุ่มจะไม่ทำอะไรจนกว่าคุณจะเข้าใจว่าคุณอยู่โหมดไหน ที่นี่ทำอะไรก็ไม่พัง

-- NORMAL -- 1,1
click the box above, then press i to start typing

Move around (NORMAL mode)

h j k lleft, down, up, right 0start of the line $end of the line wforward one word ggthe very top Gthe very bottom

Change things

iinsert here ainsert after this letter oopen a new line below xdelete one letter dwdelete one word dddelete the whole line uundo — your best friend

Save and leave (press : first)

:wwrite (save), stay here :qquit — refuses if unsaved :wqsave and quit :q!quit, throw changes away

Search

/wordsearch forward nnext match
When you are ready for the real thing: a full free course is already installed on every Linux machine. Type vimtutor and press Enter. It takes thirty minutes and it is the best half hour you can spend on this. 准备好玩真的时候:每台 Linux 机器上都已经装好了一门免费完整教程。 输入 vimtutor 回车。三十分钟,这是你能花在这件事上最值的半小时。 เมื่อคุณพร้อมสำหรับของจริง มีคอร์สฟรีเต็มรูปแบบติดตั้งอยู่บนเครื่อง Linux ทุกเครื่องแล้ว พิมพ์ vimtutor แล้วกด Enter ใช้เวลาสามสิบนาที และเป็นครึ่งชั่วโมงที่คุ้มค่าที่สุดที่คุณจะใช้กับเรื่องนี้

Try it in a real container

# Ubuntu — install it first docker run -it --rm ubuntu:24.04 bash -c \ "apt-get update -qq && apt-get install -y -qq vim && vimtutor" # Rocky — vim-enhanced, and dnf just works docker run -it --rm rockylinux:9 bash -c \ "dnf install -y -q vim-enhanced && vimtutor"

Both of those use --rm, so the container deletes itself when you leave. You can do this as many times as you like and your computer stays clean. 这两条都用了 --rm,你一退出容器就自己删掉了。 你想练几次都行,电脑还是干干净净。 ทั้งสองอันใช้ --rm คอนเทนเนอร์จะลบตัวเองเมื่อคุณออก คุณทำซ้ำกี่ครั้งก็ได้ และเครื่องของคุณยังสะอาดอยู่

🔑 SSH into a Linux container

Summary

SSH is how you get a shell on a computer that is somewhere else. You do not normally need it for a container, because docker exec is faster and safer. But building one container with a real SSH server is the best way to practise SSH itself, and it costs nothing. Learn both, and learn when each one is right.

摘要

SSH 是你在另一台电脑上拿到一个 shell 的方式。对容器来说通常并不需要它,因为 docker exec 更快也更安全。但搭一个带真 SSH 服务的容器,是练 SSH 本身最好的办法,而且不花钱。两个都学会,并且分清什么时候该用哪个。

สรุป

SSH คือวิธีเข้าไปได้เชลล์บนคอมพิวเตอร์ที่อยู่ที่อื่น ปกติคุณไม่ต้องใช้มันกับคอนเทนเนอร์ เพราะ docker exec เร็วกว่าและปลอดภัยกว่า แต่การสร้างคอนเทนเนอร์หนึ่งตัวที่มีเซิร์ฟเวอร์ SSH จริง เป็นวิธีฝึก SSH ที่ดีที่สุด และไม่ต้องเสียเงิน เรียนทั้งสองอย่าง และรู้ว่าเมื่อไรควรใช้อันไหน

Read this before you build anything. If you only want a shell inside a container you already have, you do not want SSH. Use this, and it is instant: 动手之前先看这句。如果你只是想在一个已有的容器里拿个 shell, 你需要 SSH。用下面这条,立刻就有: อ่านข้อนี้ก่อนจะสร้างอะไร ถ้าคุณแค่อยากได้เชลล์ในคอนเทนเนอร์ที่มีอยู่แล้ว คุณไม่ต้องใช้ SSH ใช้อันนี้ และได้ทันที
docker exec -it ubu bash # a shell in a running container docker exec -it rocky bash # Rocky side docker exec -u ploy -it ubu bash # as a different user docker exec ubu ls /etc # one command, no shell needed

No password, no keys, no extra program running, nothing to attack. Use docker exec every day. So why learn SSH at all? Because the moment you leave your own laptop — a rented server, a Raspberry Pi, the school's machine — SSH is the only door. 不用密码、不用密钥、不用多跑一个程序,也没有多出来的攻击面。 docker exec 是你每天该用的。那还学 SSH 干什么?因为一旦离开你自己的笔记本—— 租的服务器、树莓派、学校那台机器——SSH 是唯一的门。 ไม่ต้องมีรหัสผ่าน ไม่ต้องมีคีย์ ไม่ต้องมีโปรแกรมเพิ่ม และไม่มีช่องให้โจมตี ใช้ docker exec ทุกวัน แล้วจะเรียน SSH ไปทำไม? เพราะทันทีที่คุณออกจากแล็ปท็อปของตัวเอง — เซิร์ฟเวอร์เช่า Raspberry Pi เครื่องของโรงเรียน — SSH คือประตูเดียว

1 Make a key pair, once, for ever

SSH can use a password, but a key is better: nothing to type, nothing to guess, nothing to forget. You get two files. The one without .pub is secret and never leaves your computer. The one with .pub you give away freely. SSH 可以用密码,但密钥更好:不用打字、猜不出来、也忘不了。 你会得到两个文件:没有 .pub 的那个是私密的,永远不离开你的电脑; .pub 的那个可以随便给人。 SSH ใช้รหัสผ่านได้ แต่คีย์ดีกว่า: ไม่ต้องพิมพ์ เดาไม่ได้ และลืมไม่ได้ คุณจะได้ไฟล์สองไฟล์ ไฟล์ที่ไม่มี .pub เป็นความลับและไม่ออกจากเครื่องคุณเลย ไฟล์ที่มี .pub แจกได้อย่างอิสระ

ssh-keygen -t ed25519 -f ./labkey -N "" # labkey ← SECRET. never send this to anybody, ever. # labkey.pub ← safe to share. this is the one that goes on the server.
Permissions matter here, and SSH is strict about it. If your secret key can be read by anybody else, SSH refuses to use it and prints UNPROTECTED PRIVATE KEY FILE. Fix it with one command. 这里权限很重要,SSH 对此很严格。 如果你的私钥别人也能读,SSH 会拒绝使用它,并打印 UNPROTECTED PRIVATE KEY FILE。一条命令就能修好。 สิทธิ์ไฟล์สำคัญตรงนี้ และ SSH เข้มงวดเรื่องนี้ ถ้าคีย์ลับของคุณคนอื่นอ่านได้ SSH จะไม่ยอมใช้มัน และพิมพ์ว่า UNPROTECTED PRIVATE KEY FILE แก้ด้วยคำสั่งเดียว
chmod 600 ./labkey # only I may read it

2 Build a container that runs a real SSH server

Make a file called Dockerfile next to your keys. Every line here is needed — the /run/sshd folder in particular, because sshd refuses to start without it and the error does not say so clearly. 在密钥旁边新建一个 Dockerfile。这里每一行都是必需的—— 特别是 /run/sshd 这个文件夹:没有它 sshd 根本起不来, 而报错并不会说清楚原因。 สร้างไฟล์ชื่อ Dockerfile ไว้ข้าง ๆ คีย์ของคุณ ทุกบรรทัดที่นี่จำเป็น — เฉพาะอย่างยิ่งโฟลเดอร์ /run/sshd เพราะ sshd ไม่ยอมเริ่มถ้าไม่มีมัน และข้อความผิดพลาดไม่ได้บอกชัด

# Dockerfile — Ubuntu with a working SSH server FROM ubuntu:24.04 RUN apt-get update -qq && apt-get install -y -qq openssh-server \ && rm -rf /var/lib/apt/lists/* RUN mkdir -p /run/sshd /root/.ssh && chmod 700 /root/.ssh COPY labkey.pub /root/.ssh/authorized_keys RUN chmod 600 /root/.ssh/authorized_keys EXPOSE 22 CMD ["/usr/sbin/sshd", "-D", "-e"]
Read the last line. -D means "do not go into the background". A container lives only as long as its main program, so a service that helpfully disappears into the background kills the container instantly. -e sends the log to the screen, where docker logs can find it. 看最后一行:-D 的意思是“不要转到后台”。 容器的寿命就是它主程序的寿命,所以一个“贴心地”跑到后台去的服务,会立刻把容器杀掉。 -e 把日志送到屏幕上,这样 docker logs 才找得到。 อ่านบรรทัดสุดท้าย -D แปลว่า "อย่าไปอยู่เบื้องหลัง" คอนเทนเนอร์มีชีวิตอยู่เท่ากับโปรแกรมหลักของมัน ดังนั้นเซอร์วิสที่หลบไปอยู่เบื้องหลังอย่างมีน้ำใจ จะฆ่าคอนเทนเนอร์ทันที -e ส่งล็อกไปที่หน้าจอ ที่ซึ่ง docker logs หาเจอ
# the same thing on the Red Hat side — note the different package and path FROM rockylinux:9 RUN dnf install -y -q openssh-server && dnf clean all RUN ssh-keygen -A && mkdir -p /root/.ssh && chmod 700 /root/.ssh COPY labkey.pub /root/.ssh/authorized_keys RUN chmod 600 /root/.ssh/authorized_keys EXPOSE 22 CMD ["/usr/sbin/sshd", "-D", "-e"]

ssh-keygen -A makes the server's own host keys. Ubuntu's package does that for you; Rocky's does not, and without it sshd will not start. ssh-keygen -A 生成服务器自己的主机密钥。 Ubuntu 的包会替你做,Rocky 的不会——没有它 sshd 起不来。 ssh-keygen -A สร้างโฮสต์คีย์ของเซิร์ฟเวอร์เอง แพ็กเกจของ Ubuntu ทำให้ ของ Rocky ไม่ทำ และถ้าไม่มีมัน sshd จะไม่เริ่ม

3 Build it and run it, with a folder mounted

Port 22 is SSH's own door. On your computer port 22 may already be busy, so we knock on 2222 instead and Docker passes it through. 端口 22 是 SSH 自己的门。你电脑上的 22 端口可能已经被占用, 所以我们敲 2222,由 Docker 转进去。 พอร์ต 22 คือประตูของ SSH เอง บนเครื่องคุณพอร์ต 22 อาจถูกใช้อยู่แล้ว เราจึงเคาะที่ 2222 แทน แล้ว Docker ส่งต่อเข้าไป

docker build -t sshlab:1.0 . # Windows docker run -d --name sshbox -p 2222:22 \ -v "C:/Users/Admin/projects:/mnt/host" sshlab:1.0 # Mac / Linux docker run -d --name sshbox -p 2222:22 \ -v "$HOME/projects:/mnt/host" sshlab:1.0 docker ps --filter name=sshbox # sshbox Up 4 seconds 0.0.0.0:2222->22/tcp

4 Go in

ssh -i ./labkey -p 2222 root@localhost

This is the real output from doing exactly that: 下面是真的这样做以后得到的输出: นี่คือผลลัพธ์จริงจากการทำแบบนั้นเป๊ะ ๆ

$ ssh -i ./labkey -p 2222 root@localhost 'whoami; hostname; grep PRETTY /etc/os-release' root 69ebbae57949 PRETTY_NAME="Ubuntu 24.04.4 LTS" $ ssh -i ./labkey -p 2222 root@localhost 'cat /mnt/host/host_note.txt' hello from the host ← the mounted folder is there, over SSH
Notice the two ways to use ssh. With nothing after the address you get a shell and stay. With a command in quotes it runs that one command and comes straight back — which is how every script and every backup job in the world uses SSH. 注意 ssh 的两种用法:地址后面什么都不加,你会进去拿到一个 shell 留在里面; 后面加一条带引号的命令,它就只跑那一条然后立刻回来—— 全世界的脚本和备份任务都是这么用 SSH 的。 สังเกตสองวิธีในการใช้ ssh ถ้าไม่ใส่อะไรหลังที่อยู่ คุณจะได้เชลล์และอยู่ในนั้น ถ้าใส่คำสั่งในเครื่องหมายคำพูด มันจะรันคำสั่งนั้นแล้วกลับออกมาทันที — ซึ่งเป็นวิธีที่สคริปต์และงานสำรองข้อมูลทั้งโลกใช้ SSH

5 Move files over SSH

Once SSH works, two more programs come free. They use the same key and the same port. SSH 一通,另外两个程序就免费送你了。它们用同一个密钥、同一个端口。 พอ SSH ใช้ได้ ก็ได้อีกสองโปรแกรมมาฟรี ๆ ใช้คีย์เดียวกันและพอร์ตเดียวกัน

# scp — copy one file. Note the CAPITAL P for the port. scp -i ./labkey -P 2222 root@localhost:/etc/hostname . scp -i ./labkey -P 2222 note.txt root@localhost:/root/ # rsync — copy a whole folder, and only what changed rsync -av -e "ssh -i ./labkey -p 2222" ./site/ root@localhost:/var/www/
The capital letters are not a mistake. ssh and rsync use -p for the port. scp uses -P. Everybody gets this wrong at least once, and the error is unhelpful. 大小写不是笔误:sshrsync 用小写 -p 指端口,scp 用大写 -P。 每个人至少弄错一次,而报错也帮不上忙。 ตัวพิมพ์ใหญ่ไม่ใช่ความผิดพลาด ssh และ rsync ใช้ -p สำหรับพอร์ต ส่วน scp ใช้ -P ทุกคนพลาดข้อนี้อย่างน้อยหนึ่งครั้ง และข้อความผิดพลาดก็ไม่ช่วยอะไร

6 The warning you will definitely meet

Delete the container, build it again, and SSH will shout at you in capital letters. Nothing is wrong. The new container made itself brand new host keys, so the machine answering on port 2222 really is a different machine than the one you met yesterday — and SSH is doing its job by telling you. 删掉容器再重建,SSH 会用大写字母冲你喊。其实没出事: 新容器给自己生成了全新的主机密钥,所以现在在 2222 端口应答的确实是另一台机器, 跟你昨天见的那台不一样——SSH 提醒你,正是它在尽责。 ลบคอนเทนเนอร์แล้วสร้างใหม่ SSH จะตะโกนใส่คุณเป็นตัวพิมพ์ใหญ่ ไม่มีอะไรผิด คอนเทนเนอร์ใหม่สร้างโฮสต์คีย์ใหม่หมดให้ตัวเอง เครื่องที่ตอบอยู่บนพอร์ต 2222 จึงเป็นเครื่องอื่นจริง ๆ ไม่ใช่เครื่องที่คุณเจอเมื่อวาน — และ SSH กำลังทำหน้าที่ของมันด้วยการบอกคุณ

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Host key verification failed.

Because you know why, the fix is safe here. Forget the old key for that port: 既然你知道原因,在这里修它是安全的。把那个端口的旧密钥忘掉: เพราะคุณรู้สาเหตุ การแก้ตรงนี้จึงปลอดภัย ให้ลืมคีย์เก่าของพอร์ตนั้น

ssh-keygen -R "[localhost]:2222"

For a throwaway lab container you can also tell SSH not to keep a record at all. Never do this to a real server — that check is what stops somebody pretending to be your machine. 对于用完就扔的实验容器,你也可以叫 SSH 干脆不要记录。 千万不要对真实服务器这样做——那个检查正是防止别人假冒你的机器的东西。 สำหรับคอนเทนเนอร์ทดลองที่ใช้แล้วทิ้ง คุณจะบอก SSH ให้ไม่ต้องจดบันทึกเลยก็ได้ อย่าทำแบบนี้กับเซิร์ฟเวอร์จริงเด็ดขาด — การตรวจนั้นคือสิ่งที่กันคนอื่นจากการปลอมเป็นเครื่องของคุณ

ssh -i ./labkey -p 2222 \ -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@localhost

7 When it will not let you in

What you seeWhat it meansDo this
Connection refused Nothing is listening. sshd never started, or the container has already stopped. docker ps then docker logs sshbox. Nine times out of ten it is the missing /run/sshd.
Permission denied (publickey) The server does not accept your key. Check authorized_keys holds the .pub file and that /root/.ssh is 700.
UNPROTECTED PRIVATE KEY FILE Other people can read your secret key. chmod 600 ./labkey
REMOTE HOST IDENTIFICATION HAS CHANGED A rebuilt container has new host keys. Expected. ssh-keygen -R "[localhost]:2222"
It asks for a password you never set Your key was not offered or not accepted, so it fell back. Add -v to ssh and read which key it tried.
Do not put an SSH server in a real container. One container should run one program. Adding sshd means a second program, another password or key to look after, an open port, and a way into your system that nobody is watching. Real teams use docker exec and kubectl exec. Build the SSH container to learn SSH, then delete it. 不要在真实容器里装 SSH 服务。一个容器应该只跑一个程序。 加上 sshd 就意味着多一个程序、多一份要保管的密码或密钥、一个开着的端口, 以及一条没人盯着的进入系统的路。真实团队用 docker execkubectl exec。搭这个 SSH 容器是为了学 SSH,学完就删掉。 อย่าใส่เซิร์ฟเวอร์ SSH ในคอนเทนเนอร์จริง คอนเทนเนอร์หนึ่งตัวควรรันโปรแกรมเดียว การเพิ่ม sshd หมายถึงโปรแกรมที่สอง รหัสผ่านหรือคีย์อีกชุดที่ต้องดูแล พอร์ตที่เปิดอยู่ และทางเข้าระบบที่ไม่มีใครเฝ้า ทีมจริงใช้ docker exec และ kubectl exec สร้างคอนเทนเนอร์ SSH เพื่อเรียน SSH แล้วลบทิ้ง

The honest comparison

 docker execssh
Set-up needednonea server, a key, a port
Works oncontainers on this machineany machine, anywhere
Copy filesdocker cpscp · rsync
Extra risknonean open door to guard
Use it foreveryday container workreal servers, and practice

🧹 Put everything away

You have made containers, a volume and a network today. Here is how to leave no mess. Do this before you close the laptop. 今天你造了容器、一个数据卷和一个网络。下面是怎么不留垃圾。 合上笔记本之前做一遍。 วันนี้คุณสร้างคอนเทนเนอร์ วอลลุม และเน็ตเวิร์กไว้ นี่คือวิธีเก็บให้ไม่เหลือความรุงรัง ทำก่อนปิดเครื่อง

docker ps -a # everything, running or stopped docker rm -f ubu rocky web app sshbox # delete those containers docker rmi sshlab:1.0 # and the ssh practice image docker network rm labnet # delete the network docker volume rm labdata # delete the volume — final! docker images # the images stay, and that is fine
Keep the images. They cost disk but they save you the download next time. ubuntu:24.04 is under 80 MB. Deleting containers is the housekeeping that matters; deleting images only makes tomorrow slower. 镜像留着。它们占硬盘,但下次省下载。ubuntu:24.04 不到 80 MB。真正该收拾的是容器;删镜像只会让明天变慢。 เก็บอิมเมจไว้ มันกินพื้นที่ แต่ช่วยคุณประหยัดการดาวน์โหลดครั้งหน้า ubuntu:24.04 เล็กกว่า 80 MB การลบคอนเทนเนอร์คือการเก็บบ้านที่สำคัญ การลบอิมเมจแค่ทำให้วันพรุ่งนี้ช้าลง

🚀 Where to go next

🐳 The Docker Lab. Images, layers, ports, a React site in a box, and sharing it with the world through Tailscale.
Docker 实验室:镜像、层、端口、把 React 网站装进盒子, 再用 Tailscale 分享给全世界。

แล็บ Docker: อิมเมจ เลเยอร์ พอร์ต เว็บ React ในกล่อง และแชร์ให้คนทั้งโลกผ่าน Tailscale
Open ▸
☸️ The Kubernetes Lab. Now that you can drive one Linux box, let a machine drive a hundred of them and keep them alive while you sleep.
Kubernetes 实验室:你已经能开一台 Linux 了, 现在让一台机器去开一百台,并且在你睡觉时保持它们活着。

แล็บ Kubernetes: ตอนนี้คุณขับเครื่อง Linux หนึ่งเครื่องได้แล้ว ทีนี้ให้เครื่องจักรขับเป็นร้อยเครื่อง และดูแลให้ไม่ตายตอนคุณหลับ
Open ▸
📘 Linux — the introduction. A gentler start on what Linux is and where it came from, with activities and a test.
Linux 入门:更平缓地讲 Linux 是什么、从哪里来,还有练习和测验。

Linux เบื้องต้น: เริ่มแบบนุ่มนวลกว่าว่า Linux คืออะไรและมาจากไหน พร้อมกิจกรรมและแบบทดสอบ
Open ▸

📌 Points to remember

  • Two families, one job. Ubuntu says apt and .deb. Rocky says dnf and .rpm. Everything else is nearly the same. 两个家族,同一份活:Ubuntu 说 apt.deb, Rocky 说 dnf.rpm,其他几乎一样。 สองตระกูล งานเดียวกัน Ubuntu พูดว่า apt และ .deb Rocky พูดว่า dnf และ .rpm อย่างอื่นเกือบเหมือนกันหมด
  • CentOS is finished. Rocky and Alma replaced it. Read CentOS, write Rocky. CentOS Linux 已经结束了,请用 Rocky 或 Alma:读得懂 CentOS,动手写 Rocky。 CentOS Linux จบไปแล้ว ให้ใช้ Rocky หรือ Alma อ่าน CentOS ให้ได้ แต่เขียนด้วย Rocky
  • A fresh Ubuntu image has almost nothing in it. No vim, no ping, no ip, no curl. apt-get update first, always. 新的 Ubuntu 镜像里几乎什么都没有:没有 vim、没有 ping、没有 ip、没有 curl。 永远先 apt-get update อิมเมจ Ubuntu ใหม่แทบไม่มีอะไรเลย ไม่มี vim ไม่มี ping ไม่มี ip ไม่มี curl ให้ apt-get update ก่อนเสมอ
  • The container's own files are scratch paper. Anything you want to keep goes in a volume or a shared folder. 容器自己的文件是草稿纸:想留下的东西都放进数据卷或共享文件夹。 ไฟล์ในตัวคอนเทนเนอร์เองคือกระดาษทด อะไรที่อยากเก็บให้ใส่ในวอลลุมหรือโฟลเดอร์ที่แชร์
  • Make your own network, then use names. The default bridge has no names, and addresses change. 自己建网络,然后用名字:默认桥接网络没有名字,而地址会变。 สร้างเน็ตเวิร์กของตัวเอง แล้วใช้ชื่อ บริดจ์ปริยายไม่มีชื่อ และที่อยู่ก็เปลี่ยนได้
  • localhost in a container is the container. Not your computer, and not the container next door. 容器里的 localhost 就是这个容器:不是你的电脑,也不是隔壁那个容器。 localhost ในคอนเทนเนอร์คือตัวคอนเทนเนอร์นั้น ไม่ใช่เครื่องคุณ และไม่ใช่คอนเทนเนอร์ข้าง ๆ
  • rm has no recycle bin. ls the path first. Every time. rm 没有回收站:先用 ls 看一遍那个路径,每次都要。 rm ไม่มีถังขยะ ให้ ls ดูพาธก่อน ทุกครั้ง
  • sudo !! and Ctrl+R. Never retype a command. The history is numbered, and !42 runs number 42 again. sudo !!Ctrl+R:命令永远不要重打。 历史记录是带编号的,!42 就是再跑一遍第 42 条。 sudo !! และ Ctrl+R อย่าพิมพ์คำสั่งซ้ำ ประวัติมีเลขกำกับ และ !42 คือรันอันที่ 42 อีกครั้ง
  • Use docker exec, not SSH, for containers. Build an SSH container to learn SSH, then delete it. 对容器要用 docker exec,不要用 SSH。 搭 SSH 容器是为了学 SSH,学完就删。 กับคอนเทนเนอร์ให้ใช้ docker exec ไม่ใช่ SSH สร้างคอนเทนเนอร์ SSH เพื่อเรียน SSH แล้วลบทิ้ง
  • When lost in vim, press Esc. To leave and keep nothing: :q! 在 vim 里迷路了就按 Esc;想不保存直接走::q! ถ้าหลงทางใน vim ให้กด Esc ถ้าจะออกโดยไม่เก็บอะไร: :q!

Thank you for learning with us today. Break a container on purpose before you go — type rm somewhere silly, watch it go wrong, then throw the whole thing away and start a clean one. Nothing is lost, and that freedom is the whole reason we are learning Linux in Docker. See you in the next lab. 感谢今天和我们一起学习。走之前故意弄坏一个容器吧—— 在某个奇怪的地方敲个 rm,看它出错,然后把整个东西扔掉,重开一个干净的。 什么都不会丢,而这份自由就是我们在 Docker 里学 Linux 的全部理由。下个实验室见。 ขอบคุณที่เรียนกับเราวันนี้ ก่อนไป ลองตั้งใจทำคอนเทนเนอร์ให้พังดู — พิมพ์ rm ในที่ที่ไม่ควร ดูมันผิดพลาด แล้วทิ้งทั้งอันไป เริ่มอันใหม่ที่สะอาด ไม่มีอะไรหาย และอิสระนั้นคือเหตุผลทั้งหมดที่เราเรียน Linux ใน Docker แล้วเจอกันในแล็บหน้า