Git 是版本管理工具,GitHub 只是把仓库放在网上。 两个概念别混。
init 建仓库、add 放进暂存区、commit 记成一个版本。
commit 之后文件内容没变,变的是 .git 里多了一份快照。
部署要三个文件:requirements.txt 说要装什么、Procfile 说怎么跑、runtime.txt 指定版本。
requirements.txt 里写死版本号很重要。
上一篇让外面的人能临时访问到你的机器。这一篇把程序搬到一台一直开着的机器上。
教材第 10 章后半几节是【部署 Flask 网站程序到云端平台】和【认识版本管理工具与 Git】。
再往后是【安装与初设 Git 前端工具】【下载安装与运行 Heroku CLI】
最后一节是【设置 Heroku CLI 与发布文件】。
Git 那部分是真实跑的:git init、git add、git commit、git log 全都执行了,
.gitignore 生效也验证过。Heroku 那部分标「平台原样」,因为要账号。
有一句必须提前说:Heroku 2022 年起取消免费额度,学习时先看清计费方式再动手。
一、Git 不等于 GitHub
这两个名字天天一起出现,很多人以为是同一个东西。不是。
Git 是装在你电脑上的版本管理工具,它管的是本地的一个 .git 目录。
就算完全断网、没有任何账号,它也能正常用。
GitHub 是一个网站,它把 Git 仓库放在网上,方便协作和备份。同类的还有 GitLab、Gitee。
打个比方:Git 是本地的账本,GitHub 是帮你保管账本的那个保险箱。没有保险箱,账本照样能用。
演示第一段 git --version 打出来 2.55.0,说明这台机器装了。
Windows 上装完 Git 会带一个 Git Bash,那是个能跑 Linux 命令的终端。
二、一个仓库的三步
git init 建仓库(目录里多一个 .git),git add . 把改动放进暂存区,
git commit -m "说明" 把暂存区记成一个版本。三步。
演示里三步退出码都是 0,git log --oneline 打出一行:fa1e12d 第一个版本。
那串字母数字是这次提交的编号,叫 commit hash。
commit 之后文件内容并没有变——变的是 .git 里多了一份快照。
这就是为什么你可以随时回到任何一个 commit 的状态。
「暂存区」这个概念一开始很别扭。为什么不能 add 和 commit 合成一步?
因为它允许你「只提交一部分改动」——改了五个文件,这次只想提交其中两个。
日常最常用的四个命令:git status(现在什么状态)、git diff(改了什么)、
git log --oneline(历史)、git add 加 git commit(记一个版本)。
git status 是最该先敲的那个。 它告诉你哪些文件改了、哪些还没提交,不用猜。
三、什么不该进仓库
在项目根目录建一个 .gitignore,把不该进仓库的东西列进去。
演示加了之后,git status 只看得到 .gitignore 自己。
最该排除的三类。
第一类是 venv/:里面几万个文件,别人能自己建,而且里面的路径是按你的机器写的,
推上去只会把仓库撑大。
第二类是 __pycache__/ 和 *.pyc:那是编译缓存,每次跑都会重新生成,提交上去纯属噪音。
第三类也是最要紧的:带密码的文件。 key.json(第 7.2 篇那个 Google 服务账号)、.env、
数据库密码、API Token。
一旦推上去,就算立刻删掉,历史里还留着。 别人 clone 下来往回翻就能看到。
GitHub 上每天都有密钥因此泄露。
真发生了怎么办?改密码是第一位的——密钥已经泄露了,删文件救不回来。
然后再用 git filter-repo 之类的工具清理历史。
四、部署要的三样东西
| 文件 | 写什么 | 作用 |
|---|---|---|
requirements.txt |
flask==3.1.3 |
告诉服务器要装哪些包、装哪个版本 |
Procfile |
web: python app.py |
告诉平台「怎么把你的程序跑起来」 |
runtime.txt |
python-3.13.14 |
指定 Python 版本(可选) |
三个文件都放在项目根目录,跟 app.py 平级。
文件名一个字都不能错——平台是按名字找的,不是按内容猜的。
requirements.txt 里写死版本号很重要。 只写 flask,半年后平台装了个新版本,
你的代码可能就跑不起来了——而你自己机器上什么都没改。
写死的方法:flask==3.1.3。
生成它的办法是在虚拟环境里 pip freeze > requirements.txt,它会把当前环境所有包和版本都列出来。
pip freeze 的缺点是把一堆不相干的包也写进去(比如你调试时装的)。
更干净的做法是手写,只写真正需要的。
五、部署的完整动作
heroku login 登录,heroku create 应用名 建一个应用,git push heroku main 推代码,
heroku open 打开网址,heroku logs --tail 看日志。
注意第三个命令还是 git push,只是推到了 heroku 这个远程仓库。
所以会 Git 和会部署,在这里是同一件事。
平台收到代码后做四件事:按 requirements.txt 装包、按 runtime.txt 选 Python 版本、
按 Procfile 启动、然后给你一个网址。
这就是「部署」的全部:把你的代码搬到一台一直开着的机器上,让它按 Procfile 里写的方式跑起来,
再对外开一个端口。
这一段在演示里标了「平台原样 · 本沙箱不执行」——要 Heroku 账号。
而且 Heroku 2022 年起取消免费额度,学习时先看清计费方式再动手。
不想花钱也有别的选择(Render、Fly.io、PythonAnywhere 都有免费档),流程大同小异:
推代码、给配置、看日志。
六、最容易翻车的三处
| 现象 | 多半是 | 怎么查 |
|---|---|---|
| 启动就崩 | requirements.txt 少写了包 |
heroku logs --tail 看报错栈 |
| 能跑但访问超时 | 端口没读环境变量 PORT |
平台上端口由环境变量给,不能写死 |
| 文件上传后不见了 | 本地磁盘是临时的 | 要持久化得用对象存储或数据库 |
第二条最坑。 本地跑 python app.py 默认 5000 端口,平台却会给你分配一个随机端口,
放在环境变量 PORT 里。
必须写成 port = int(os.environ.get('PORT', 5000))。
不改的话,平台一直等不到你的服务起来,最后报超时——而日志里什么都看不出来。
第三条也常踩。
很多平台的文件系统是临时的:重启一次,你上传的文件全没了。 这不是 bug,
是设计——容器随时可能被销毁重建。
要存住文件,得用对象存储(第 11 章会碰到)或者数据库(第 12 章)。 「本地磁盘能写」这件事,在云上是不成立的。
七、动手 Demo:Git 与部署:把代码推到别人的机器上
先看这台机器有没有 git,再在一个临时目录里建仓库、提交、看历史。
然后加 .gitignore,看 git status 少掉了哪些东西。
最后把部署要的三个文件真写出来看内容。Heroku 那几条命令标平台原样。
import os, subprocess, tempfile
def git(*args, cwd=None):
r = subprocess.run(['git'] + list(args), capture_output=True, text=True, cwd=cwd)
return r.returncode, (r.stdout + r.stderr).strip()
print('① 先看有没有 git')
rc, out = git('--version')
print(' git --version →', out if rc == 0 else '没装')
print(' Git 是版本管理工具,不是 GitHub。GitHub 只是把 Git 仓库放在网上。两个概念别混。')
d = tempfile.mkdtemp()
open(os.path.join(d, 'app.py'), 'w').write("print('hi')\n")
os.makedirs(os.path.join(d, 'venv', 'Lib'), exist_ok=True)
open(os.path.join(d, 'venv', 'Lib', 'a.py'), 'w').write('x = 1\n')
os.makedirs(os.path.join(d, '__pycache__'), exist_ok=True)
open(os.path.join(d, '__pycache__', 'a.pyc'), 'w').write('bin')
open(os.path.join(d, 'key.json'), 'w').write('{"secret": "don\'t commit"}')
print('② 一个仓库的三步')
git('init', '-q', cwd=d)
print(' git init -q 退出码 %d' % git('init', '-q', cwd=d)[0])
git('add', 'app.py', cwd=d)
print(' git add . 退出码 %d' % git('add', '.', cwd=d)[0])
print(' git commit -q -m 第一个版本 退出码 %d'
% git('-c', 'user.name=demo', '-c', '[email protected]',
'commit', '-q', '-m', '第一个版本', cwd=d)[0])
print(' git log --oneline:')
for ln in git('log', '--oneline', cwd=d)[1].splitlines():
print(' ' + ln)
print(' init 建仓库(多一个 .git 目录),add 把改动放进暂存区,commit 把暂存区记成一个版本。')
print(' commit 之后文件内容并没有变——变的是 .git 里多了一份快照。')
print('③ 什么东西不该进仓库')
open(os.path.join(d, '.gitignore'), 'w').write('venv/\n__pycache__/\n*.pyc\nkey.json\n')
print(' 加了 .gitignore 之后,git status 只看到:')
for ln in git('status', '--porcelain', cwd=d)[1].splitlines():
print(' ' + ln)
print(' venv 目录里几万个文件不该进仓库:别人能自己建,而且里面的路径是按你的机器写的。')
print(' key.json、.env 这种带密码的文件尤其不能进。')
print(' 一旦推上去,就算立刻删掉,历史里还留着。')
print('④ 部署要的三样东西')
print('| 文件 | 写什么 | 作用 |')
print('|---|---|---|')
print('| requirements.txt | flask==3.1.3 | 告诉服务器要装哪些包、装哪个版本 |')
print('| Procfile | web: python app.py | 告诉平台「怎么把你的程序跑起来」 |')
print('| runtime.txt | python-3.13.14 | 指定 Python 版本(可选,平台常自带) |')
open(os.path.join(d, 'requirements.txt'), 'w').write('flask==3.1.3\n')
open(os.path.join(d, 'Procfile'), 'w').write('web: python app.py\n')
print(" requirements.txt 内容:%r" % open(os.path.join(d, 'requirements.txt')).read().strip())
print(" Procfile 内容:%r" % open(os.path.join(d, 'Procfile')).read().strip())
print(' requirements.txt 里写死版本号很重要。')
print('⑤ 部署的完整动作')
print(' [平台原样 · 本沙箱不执行]')
print(' | # 本书第 10-6、10-7 节(平台原样,本沙箱不执行:要 Heroku 账号)')
print(' | heroku login')
print(' | heroku create my-flask-app')
print(' | git push heroku main')
print(' | heroku open')
print(' | heroku logs --tail')
print(' Heroku 2022 年起取消免费额度,学习时先看清计费方式再动手')
print(' 注意第三个命令还是 git push,只是推到了 heroku 这个远程仓库。')
print('⑥ 部署最容易翻车的三处')
print('| 现象 | 多半是 | 怎么查 |')
print('|---|---|---|')
print('| 启动就崩 | requirements.txt 少写了包 | heroku logs --tail 看报错栈 |')
print('| 能跑但访问超时 | 端口没读环境变量 PORT | 平台上端口由环境变量给,不能写死 5000 |')
print('| 文件上传后不见了 | 本地磁盘是临时的 | 要持久化得用对象存储或数据库 |')
print(" 正确写法:port = int(os.environ.get('PORT', 5000))")
实跑输出:
--------------------------------------------------------------------
Git 与部署:把代码推到别人的机器上
--------------------------------------------------------------------
① 先看有没有 git
--------------------------------------------------------------------
git --version → git version 2.55.0.windows.3
Git 是版本管理工具,不是 GitHub。GitHub 只是把 Git 仓库放在网上。两个概念别混。
② 一个仓库的三步
--------------------------------------------------------------------
git init -q 退出码 0
git add . 退出码 0
git commit -q -m 第一个版本 退出码 0
git log --oneline:
fa1e12d 第一个版本
init 建仓库(多一个 .git 目录),add 把改动放进暂存区,commit 把暂存区记成一个版本。三步。
commit 之后文件内容并没有变——变的是 .git 里多了一份快照。
所以你可以随时回到任何一个 commit 的状态,这就是「版本管理」的全部意义。
③ 什么东西不该进仓库
--------------------------------------------------------------------
加了 .gitignore 之后,git status 只看到:
?? .gitignore
venv 目录里几万个文件不该进仓库:别人能自己建,而且里面的路径是按你的机器写的,
推上去只会把仓库撑大。
key.json、.env 这种带密码的文件尤其不能进。
一旦推上去,就算立刻删掉,历史里还留着——GitHub 上每天都有密钥因此泄露。
④ 部署要的三样东西
--------------------------------------------------------------------
| 文件 | 写什么 | 作用 |
|---|---|---|
| requirements.txt | flask==3.1.3 | 告诉服务器要装哪些包、装哪个版本 |
| Procfile | web: python app.py | 告诉平台「怎么把你的程序跑起来」 |
| runtime.txt | python-3.13.14 | 指定 Python 版本(可选,平台常自带) |
requirements.txt 内容:'flask==3.1.3'
Procfile 内容:'web: python app.py'
requirements.txt 里写死版本号很重要。
只写 flask,半年后平台装了个新版本,你的代码可能就跑不起来了——而你自己机器上什么都没改。
⑤ 部署的完整动作
--------------------------------------------------------------------
[平台原样 · 本沙箱不执行]
| # 本书第 10-6、10-7 节(平台原样,本沙箱不执行:要 Heroku 账号)
| heroku login
| heroku create my-flask-app
| git push heroku main
| heroku open
| heroku logs --tail
Heroku 2022 年起取消免费额度,学习时先看清计费方式再动手
注意第三个命令还是 git push,只是推到了 heroku 这个远程仓库。
平台收到代码后按 Procfile 装包、启动,然后给你一个网址。
这就是「部署」的全部:把你的代码搬到一台一直开着的机器上,让它按 Procfile 里写的方式跑起来,
再对外开一个端口。
⑥ 部署最容易翻车的三处
--------------------------------------------------------------------
| 现象 | 多半是 | 怎么查 |
|---|---|---|
| 启动就崩 | requirements.txt 少写了包 | heroku logs --tail 看报错栈 |
| 能跑但访问超时 | 端口没读环境变量 PORT | 平台上端口由环境变量给,不能写死 5000 |
| 文件上传后不见了 | 本地磁盘是临时的 | 要持久化得用对象存储或数据库 |
第二条最坑:本地跑 python app.py 默认 5000 端口,平台却会给你分配一个随机端口,
必须写成 os.environ.get('PORT', 5000)。不改的话,平台一直等不到你的服务起来,最后报超时。
第二段三步都真跑了。
注意 commit 那一步多带了 user.name 和 user.email,因为临时目录里没配身份,Git 会拒绝提交。
你本机配过一次就不用每次带。
第三段那个 git status --porcelain 输出只有一行 ?? .gitignore,说明 venv、pycache、
key.json 全被忽略了。要是没加 .gitignore,这里会列出一堆。
第四段那两个文件内容要看清:requirements 写的是 flask==3.1.3,双等号锁版本;
Procfile 写的是 web: python app.py,冒号后面必须有空格。
第六段第二条最坑。
平台给你的端口是随机的,放在环境变量 PORT 里,写死 5000 的话平台一直等不到服务起来,最后报超时,
而日志里什么都看不出来。
30 秒实验
- 在一个真实项目目录里跑
git init、git status,看它列出了哪些文件, 再想想哪些该加进.gitignore。 - 在虚拟环境里跑
pip freeze,看它列了多少个包,再想想为什么手写requirements.txt更干净。 - 把演示第二段的 commit 命令去掉那两行
-c user.name,看它报什么错。
常见误区
- 「Git 和 GitHub 是一回事。」Git 是本地的工具,GitHub 是放仓库的网站。 断网、没账号,Git 照样能用。
- 「commit 会改文件内容。」不改。它是在
.git里多存一份快照。 工作目录里的文件一直是你写的样子。 - 「密钥提交了再删掉就没事。」历史里还留着。 别人 clone 下来往回翻就能看到。 第一件事是改密码。
自测三问(点开对答案)
1. Git 和 GitHub 是什么关系? —— Git 是装在本地的版本管理工具,管的是项目里那个 `.git` 目录,断网也能用。 GitHub 是一个网站,把 Git 仓库放到网上,方便协作和备份。GitLab、Gitee 是同类。 没有 GitHub,Git 照样工作。 2. 为什么 `requirements.txt` 里要写 `flask==3.1.3` 而不是只写 `flask`? —— 因为不锁版本的话,平台装包时会拿当时的最新版。 半年后出了新版本,你的代码可能就跑不起来了——而你自己机器上什么都没改。 锁版本保证「部署出来的环境和你本地一致」。 3. 本地跑得好好的,部署上去访问超时,最可能是什么原因? —— 端口写死了。本地默认 5000,云平台会给你分配一个随机端口,放在环境变量 `PORT` 里。 要写成 `int(os.environ.get('PORT', 5000))`。 不改的话平台一直等不到服务起来,最后报超时,日志里还看不出问题。第 10 章结束。下一篇进第 11 章:让程序自己去网上抓东西。
- ☐ (本篇完,下一篇 11.1 HTTP 请求与抓取图片)