乐于分享
好东西不私藏

在我电脑能跑,你那却报错?试试 Docker

在我电脑能跑,你那却报错?试试 Docker

你是不是也遇到过这种尴尬

"在我电脑上明明能跑啊!"——这句话,几乎每个写过代码的人都说过。同事把你写的程序拷过去,双击一运行,哗啦一片红字;你自己换台电脑,重装一遍 Python,发现版本不对、缺了个库、路径还写死了,折腾半天还是起不来。我见过有人写了个爬虫,自己电脑上跑得欢,发给朋友半天起不来,查了半天才发现是系统不同、连编码处理都对不上。

更崩溃的是部署。你本地调好的小网站,传到服务器上死活起不来,查了俩小时才发现服务器装的是 Python 3.9,而你用了 3.12 才有的语法。这种"环境不一致"的破事,不知道消耗了多少人的头发。

今天聊的 Docker,就是专门治这个的。它不玄乎,一句话概括:把你的代码和它的运行环境,打包成一个"盒子",到哪都能原样跑。

Docker 到底是个啥

你可以把 Docker 想象成集装箱。以前运货,每家公司的箱子尺寸不一,码头装卸乱成一锅粥。后来有了标准集装箱,管你装的是冰箱还是香蕉,箱子长得一样,吊车直接搬,火车轮船随便换。

Docker 干的事差不多:它给你的程序做一个标准"容器"(container),里面装着代码、运行环境、依赖库、配置文件,统统打包好。这个容器搬到任何装了 Docker 的机器上,都能一模一样地跑,不会因为"你那台机器少装了个啥"而翻车。

这里容易混两个词:镜像(image) 和 容器(container)。简单说,镜像就是"打包好的模板",是静态的文件;容器是镜像"跑起来"之后的那个活实例。一个镜像可以同时跑出好几个容器,就像同一张光盘可以装进多台电脑。

它和虚拟机有啥不一样

很多人一听"容器""隔离",就想到虚拟机。确实像,但轻太多了。虚拟机是连操作系统一起虚拟,一个虚拟机动辄几个 G,开机都要等半天;Docker 容器共享宿主机的系统内核,只在上面隔出独立空间,启动是秒级,体积也小得多。

打个比方:虚拟机像在楼里另租整套房,连地基带装修都得自己来;容器像在同栋楼里隔出独立单间,水电(系统内核)是和大家共用的。所以一台电脑能同时跑几十个容器,却跑不了几个虚拟机。写代码练手,容器完全够用。

先让它跑起来:一句话试试水

别急着写代码,咱们先用最省事的方式感受一下。装好 Docker 之后(官网 docker.com 下载 Desktop 版,一路下一步就行),打开终端敲:

docker --version

docker run hello-world

第一行是看版本,确认装上了。第二行是 Docker 的"Hello World"——它会去网上拉一个极小的小镜像,跑一下打印句欢迎语,然后退出。你能看到输出,就说明 Docker 通了。

第一次跑会稍微慢一点,因为它要下载那个小镜像。下完之后,你电脑里就多了一个叫 hello-world 的镜像。

动手:把你的 Python 程序装进盒子

光跑别人的是不过瘾的。咱们拿一个最经典的 Flask 小网站练手——它启动后,浏览器访问就能看到一句话。先准备三个文件。

第一个,程序本体 app.py

from flask import Flask

app = Flask(__name__)

@app.route("/")

def home():

    return "你好,这是我用 Docker 跑起来的第一个网站!"

if __name__ == "__main__":

    app.run(host="0.0.0.0", port=5000)

第二个,依赖清单 requirements.txt,告诉 Docker 需要装哪些库:

flask==3.0.3

第三个,最关键的 Dockerfile(注意大小写,没有后缀),它相当于"打包说明书":

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]

咱们一句句拆开看。FROM python:3.11-slim 是选一个基础镜像,相当于"先拿一个已经装好 Python 3.11 的干净系统当底子",slim 表示精简版,体积小;WORKDIR /app 是进到容器里的 /app 目录干活;COPY requirements.txt . 把依赖清单拷进去;RUN pip install 在容器里安装 Flask;COPY . . 把当前目录所有文件拷进容器;EXPOSE 5000 声明程序用 5000 端口;CMD 是容器启动后要执行的命令,也就是跑我们的 app.py

写好这三个文件,放在同一个文件夹里,在这个文件夹下打开终端,开始打包:

docker build -t myfirstapp .

docker run -p 5000:5000 myfirstapp

docker build 是"按 Dockerfile 造镜像",-t myfirstapp 给镜像起个名字,最后那个 . 表示用当前目录的 Dockerfile。docker run 是"把镜像跑成容器",-p 5000:5000 意思是把电脑的 5000 端口,映射到容器里的 5000 端口。

跑起来之后,浏览器打开 http://localhost:5000,就能看到那句"你好,这是我用 Docker 跑起来的第一个网站!"。第一次成功那一刻,真的挺爽的。顺手再建一个 .dockerignore 文件,把不需要打包的东西排除掉,比如本地虚拟环境 venv/、缓存 __pycache__/.git。不排的话,这些冗余文件会被一起塞进镜像,白白变大。它的写法和 git 的 .gitignore 一样,一行一个。

另外,镜像不一定要自己从头写。Docker Hub 上别人已经打好了海量现成镜像,比如 docker pull nginx 就能拉一个现成的网页服务器,省去自己配的功夫。

常用命令,记这几个就够了

别被一堆命令吓到,日常用得着的就这几个:

docker ps

docker images

docker stop 容器ID前几位

docker rm 容器ID前几位

docker rmi 镜像名

docker ps 看正在跑的容器(加 -a 能看到包括已停止的所有容器);docker images 看你本地有哪些镜像;docker stop 把跑着的容器停掉;docker rm 删掉停止的容器;docker rmi 删掉不用的镜像,腾空间。容器 ID 不用全抄,敲前三四位能区分就行。

新手最容易踩的坑

镜像太大,下载慢。 基础镜像别用 python:3.11(完整版近 1 个 G),换成 python:3.11-slim 能小一大半,练手够用了。

浏览器打不开网页。 八成是忘了端口映射。容器是独立的小世界,不映射端口,你电脑根本访问不进去。记住 docker run -p 本机端口:容器端口 这个格式,别漏了。

改了代码重新跑,发现还是老样子。 因为镜像是按老代码打的。改完代码必须重新 docker build 再 run,旧容器记得先 stop 再 rm,不然端口被占着起不来。

依赖没写进 requirements.txt。 很多人本地 pip install 装了库,却没记到 requirements.txt 里,结果别人一打包就缺库。养成习惯:装一个库,就顺手写进依赖清单。

本地改了代码,容器里却没变。 容器跑的是打包时拷进去的那份代码,你后来在电脑上改的原文件它根本看不到。记住这条闭环:本地改完 → 重新 build → 重新 run,三步一条龙,顺序别乱。

不过说句大实话

Docker 不是万能药。你只是写个几十行的小脚本自己用,硬上 Docker 反而多一层折腾;只有团队协作用、要部署上线、或者需要别人精确复现你的环境时,它才真正值回票价。工具是为人服务的,别为了用而用。

总结一下

Docker 不是什么黑魔法,它解决的就是一个最朴素的问题:让程序在"我这儿"和"你那儿"表现一致。 你今天学到的,其实已经够把任意一个 Python 小项目打包发给别人、或者丢上服务器跑了——这不就是传说中的"一次打包,到处运行"嘛。

当然,Docker 能玩的远不止这些。比如用 docker-compose 一键拉起"网站+数据库"一整套,或者把镜像推到云上仓库随时取用。但这些是后话,先把今天这个"装盒—打包—运行"的闭环跑顺,你就已经迈过那道最吓人的门槛了。

回去把你之前写过的某个小脚本,试着用 Docker 装进盒子跑一次?跑通了你会发现,部署这件事,好像也没那么可怕。

如果还想继续往前走,建议按顺序试试这三步:用 docker-compose 把"网站+数据库"等多个容器一键串起来、把做好的镜像推到 Docker Hub 或阿里云仓库存着、最后丢到云服务器上 docker run 一把完成部署。到那时候,你就不再是被环境折磨的人,而是能从容交付的人了。