[{"content":"2025 年，模型接口越来越多，但个人工具并不需要同时绑定很多 SDK。我做了一次轻量化尝试：在业务代码和模型服务之间增加一个很薄的适配层，只约定输入、输出和错误格式。\n只保留真正需要的接口 适配层只做三件事：接收消息列表、选择模型、返回文本和用量信息。它不保存会话，也不把供应商特有的字段泄漏到业务代码中。这样更换服务商时，只需要替换适配层，而不是重写整个工具。\ndef ask(messages, model=\u0026#34;small-fast\u0026#34;): response = client.responses.create( model=model, input=messages, timeout=30, ) return { \u0026#34;text\u0026#34;: response.output_text, \u0026#34;usage\u0026#34;: response.usage, } 真实项目里还需要处理超时、限流和重试。重试只针对明确的临时错误，并设置上限；如果请求已经产生费用，就不能无条件重复发送。\n把日志做成能回答问题的东西 我只记录请求时间、模型名、耗时、状态码和 token 数，不记录完整提示词或敏感内容。发生问题时，这些字段足以判断是网络慢、服务拒绝，还是输入本身太大。日志的目标是帮助排错，而不是把所有数据都复制一份。\n低配服务器上的取舍 适配层可以用一个很小的 Python 或 Go 服务运行，也可以在静态站点之外单独部署。博客本身不需要调用 AI 才能展示，因此把两者拆开后，即使接口服务暂时停止，文章页面仍然可以正常访问。\n这次实践让我更确定一件事：个人项目的“智能化”不应以堆叠组件为起点。先把边界、超时、日志和成本控制做好，剩下的能力才有稳定的落脚点。\n","permalink":"https://3qiao.vip/posts/2025-ai-api-tooling/","summary":"\u003cp\u003e2025 年，模型接口越来越多，但个人工具并不需要同时绑定很多 SDK。我做了一次轻量化尝试：在业务代码和模型服务之间增加一个很薄的适配层，只约定输入、输出和错误格式。\u003c/p\u003e\n\u003ch2 id=\"只保留真正需要的接口\"\u003e只保留真正需要的接口\u003c/h2\u003e\n\u003cp\u003e适配层只做三件事：接收消息列表、选择模型、返回文本和用量信息。它不保存会话，也不把供应商特有的字段泄漏到业务代码中。这样更换服务商时，只需要替换适配层，而不是重写整个工具。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003eask\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emessages\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;small-fast\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eresponse\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eclient\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eresponses\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecreate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003emodel\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003einput\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003emessages\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003etimeout\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e30\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;text\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eoutput_text\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;usage\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eusage\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e真实项目里还需要处理超时、限流和重试。重试只针对明确的临时错误，并设置上限；如果请求已经产生费用，就不能无条件重复发送。\u003c/p\u003e\n\u003ch2 id=\"把日志做成能回答问题的东西\"\u003e把日志做成能回答问题的东西\u003c/h2\u003e\n\u003cp\u003e我只记录请求时间、模型名、耗时、状态码和 token 数，不记录完整提示词或敏感内容。发生问题时，这些字段足以判断是网络慢、服务拒绝，还是输入本身太大。日志的目标是帮助排错，而不是把所有数据都复制一份。\u003c/p\u003e\n\u003ch2 id=\"低配服务器上的取舍\"\u003e低配服务器上的取舍\u003c/h2\u003e\n\u003cp\u003e适配层可以用一个很小的 Python 或 Go 服务运行，也可以在静态站点之外单独部署。博客本身不需要调用 AI 才能展示，因此把两者拆开后，即使接口服务暂时停止，文章页面仍然可以正常访问。\u003c/p\u003e\n\u003cp\u003e这次实践让我更确定一件事：个人项目的“智能化”不应以堆叠组件为起点。先把边界、超时、日志和成本控制做好，剩下的能力才有稳定的落脚点。\u003c/p\u003e","title":"把 AI 接口接入个人工具：一次轻量化实践"},{"content":"静态站点已经很快，但默认配置通常只完成了“能访问”。2023 年我给一个个人页面做优化时，没有引入额外的缓存服务，而是先把 Nginx 的基础能力用好。\n给不同类型的文件不同缓存时间 带哈希文件名的 CSS、JavaScript 和图片可以长时间缓存；HTML 则应该短一些，方便发布后及时生效。下面是一段适合静态站点的配置：\nlocation ~* \\\\.(css|js|png|jpg|jpeg|gif|svg|webp|woff2)$ { expires 30d; add_header Cache-Control \u0026#34;public, max-age=2592000, immutable\u0026#34;; } location / { try_files $uri $uri/ /404.html; add_header X-Content-Type-Options \u0026#34;nosniff\u0026#34; always; add_header Referrer-Policy \u0026#34;strict-origin-when-cross-origin\u0026#34; always; } immutable 只适合文件内容一旦变化就会改名的资源。如果站点仍然使用固定的 main.css，缓存时间就不应设置得过长，否则更新样式后用户可能要手动刷新。\n压缩要适度 HTML、CSS、JavaScript 和 SVG 都适合启用 gzip。图片压缩则应该在构建阶段完成，不能指望 Nginx 在每次请求时临时处理大图片。对个人博客来说，少量清晰的图片比堆叠复杂的优化插件更容易维护。\n用响应头建立最低限度的安全边界 X-Content-Type-Options 可以避免浏览器猜测文件类型，Referrer-Policy 则减少从站点跳转时泄露完整路径。它们不能替代应用安全检查，却能以很低的成本补上基础防线。\n配置变更后先检查语法，再平滑加载：\nnginx -t nginx -s reload 优化静态站点时，我通常先看真实请求的响应头和缓存命中情况，再决定是否需要更复杂的 CDN 或服务端缓存。简单的站点，简单的链路往往更稳定。\n","permalink":"https://3qiao.vip/posts/2023-nginx-static-cache/","summary":"\u003cp\u003e静态站点已经很快，但默认配置通常只完成了“能访问”。2023 年我给一个个人页面做优化时，没有引入额外的缓存服务，而是先把 Nginx 的基础能力用好。\u003c/p\u003e\n\u003ch2 id=\"给不同类型的文件不同缓存时间\"\u003e给不同类型的文件不同缓存时间\u003c/h2\u003e\n\u003cp\u003e带哈希文件名的 CSS、JavaScript 和图片可以长时间缓存；HTML 则应该短一些，方便发布后及时生效。下面是一段适合静态站点的配置：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-nginx\" data-lang=\"nginx\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003elocation\u003c/span\u003e \u003cspan class=\"p\"\u003e~\u003c/span\u003e\u003cspan class=\"sr\"\u003e*\u003c/span\u003e \u003cspan class=\"s\"\u003e\\\\.(css|js|png|jpg|jpeg|gif|svg|webp|woff2)\u003c/span\u003e$ \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kn\"\u003eexpires\u003c/span\u003e \u003cspan class=\"s\"\u003e30d\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kn\"\u003eadd_header\u003c/span\u003e \u003cspan class=\"s\"\u003eCache-Control\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;public,\u003c/span\u003e \u003cspan class=\"s\"\u003emax-age=2592000,\u003c/span\u003e \u003cspan class=\"s\"\u003eimmutable\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003elocation\u003c/span\u003e \u003cspan class=\"s\"\u003e/\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kn\"\u003etry_files\u003c/span\u003e \u003cspan class=\"nv\"\u003e$uri\u003c/span\u003e \u003cspan class=\"nv\"\u003e$uri/\u003c/span\u003e \u003cspan class=\"s\"\u003e/404.html\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kn\"\u003eadd_header\u003c/span\u003e \u003cspan class=\"s\"\u003eX-Content-Type-Options\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;nosniff\u0026#34;\u003c/span\u003e \u003cspan class=\"s\"\u003ealways\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kn\"\u003eadd_header\u003c/span\u003e \u003cspan class=\"s\"\u003eReferrer-Policy\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;strict-origin-when-cross-origin\u0026#34;\u003c/span\u003e \u003cspan class=\"s\"\u003ealways\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003eimmutable\u003c/code\u003e 只适合文件内容一旦变化就会改名的资源。如果站点仍然使用固定的 \u003ccode\u003emain.css\u003c/code\u003e，缓存时间就不应设置得过长，否则更新样式后用户可能要手动刷新。\u003c/p\u003e\n\u003ch2 id=\"压缩要适度\"\u003e压缩要适度\u003c/h2\u003e\n\u003cp\u003eHTML、CSS、JavaScript 和 SVG 都适合启用 gzip。图片压缩则应该在构建阶段完成，不能指望 Nginx 在每次请求时临时处理大图片。对个人博客来说，少量清晰的图片比堆叠复杂的优化插件更容易维护。\u003c/p\u003e\n\u003ch2 id=\"用响应头建立最低限度的安全边界\"\u003e用响应头建立最低限度的安全边界\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eX-Content-Type-Options\u003c/code\u003e 可以避免浏览器猜测文件类型，\u003ccode\u003eReferrer-Policy\u003c/code\u003e 则减少从站点跳转时泄露完整路径。它们不能替代应用安全检查，却能以很低的成本补上基础防线。\u003c/p\u003e\n\u003cp\u003e配置变更后先检查语法，再平滑加载：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enginx -t\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enginx -s reload\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e优化静态站点时，我通常先看真实请求的响应头和缓存命中情况，再决定是否需要更复杂的 CDN 或服务端缓存。简单的站点，简单的链路往往更稳定。\u003c/p\u003e","title":"用 Nginx 为静态站点加上缓存与安全响应头"},{"content":"很多个人项目最初只有一个脚本：在电脑上可以运行，换一台机器就要重新安装依赖、修改配置，再手动启动。2021 年我给一个小工具做整理时，决定把“运行环境”也作为项目的一部分保存下来。\n先把服务边界说清楚 我先把程序拆成三个部分：应用本身、持久化数据、配置文件。应用容器可以随时重建，数据目录需要长期保留，配置则通过环境变量注入。这样做的好处是，升级应用时不必碰数据，迁移服务器时也只需要打包数据目录和 compose 文件。\n一个最小的 docker-compose.yml 可以这样写：\nversion: \u0026#34;3.8\u0026#34; services: app: image: example/app:1.0 restart: unless-stopped ports: - \u0026#34;8080:8080\u0026#34; env_file: - .env volumes: - ./data:/var/lib/app restart: unless-stopped 让机器重启后服务自动恢复；版本号固定则避免“今天能跑、明天突然变样”。开发阶段可以使用 latest，生产环境最好明确写版本。\n配置和数据分开 密码、令牌等内容不应该提交到 Git。项目里只保留 .env.example，真正的 .env 放在服务器上，并限制文件权限：\ncp .env.example .env chmod 600 .env docker compose up -d 备份也围绕数据目录进行。对小项目来说，每天压缩一次数据并保留最近七份，已经比“只在出问题时想起来备份”可靠很多。\n让部署变成一个动作 最后我把更新流程固定为：拉取代码、检查配置、构建新镜像、重新创建容器、查看日志。步骤固定之后，部署不再依赖某个人记得哪些命令，也更容易在低配服务器上控制资源。\n这个实践让我意识到，轻量化不只是少装软件，更是让每个组件的职责足够单一。服务越小，备份、迁移和排错都越容易。\n","permalink":"https://3qiao.vip/posts/2021-lightweight-deployment/","summary":"\u003cp\u003e很多个人项目最初只有一个脚本：在电脑上可以运行，换一台机器就要重新安装依赖、修改配置，再手动启动。2021 年我给一个小工具做整理时，决定把“运行环境”也作为项目的一部分保存下来。\u003c/p\u003e\n\u003ch2 id=\"先把服务边界说清楚\"\u003e先把服务边界说清楚\u003c/h2\u003e\n\u003cp\u003e我先把程序拆成三个部分：应用本身、持久化数据、配置文件。应用容器可以随时重建，数据目录需要长期保留，配置则通过环境变量注入。这样做的好处是，升级应用时不必碰数据，迁移服务器时也只需要打包数据目录和 compose 文件。\u003c/p\u003e\n\u003cp\u003e一个最小的 \u003ccode\u003edocker-compose.yml\u003c/code\u003e 可以这样写：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003eversion\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;3.8\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003eservices\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"nt\"\u003eapp\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003eimage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003eexample/app:1.0\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003erestart\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003eunless-stopped\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003eports\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e      \u003c/span\u003e- \u003cspan class=\"s2\"\u003e\u0026#34;8080:8080\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003eenv_file\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e      \u003c/span\u003e- \u003cspan class=\"l\"\u003e.env\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003evolumes\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e      \u003c/span\u003e- \u003cspan class=\"l\"\u003e./data:/var/lib/app\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003erestart: unless-stopped\u003c/code\u003e 让机器重启后服务自动恢复；版本号固定则避免“今天能跑、明天突然变样”。开发阶段可以使用 \u003ccode\u003elatest\u003c/code\u003e，生产环境最好明确写版本。\u003c/p\u003e\n\u003ch2 id=\"配置和数据分开\"\u003e配置和数据分开\u003c/h2\u003e\n\u003cp\u003e密码、令牌等内容不应该提交到 Git。项目里只保留 \u003ccode\u003e.env.example\u003c/code\u003e，真正的 \u003ccode\u003e.env\u003c/code\u003e 放在服务器上，并限制文件权限：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecp .env.example .env\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003echmod \u003cspan class=\"m\"\u003e600\u003c/span\u003e .env\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker compose up -d\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e备份也围绕数据目录进行。对小项目来说，每天压缩一次数据并保留最近七份，已经比“只在出问题时想起来备份”可靠很多。\u003c/p\u003e\n\u003ch2 id=\"让部署变成一个动作\"\u003e让部署变成一个动作\u003c/h2\u003e\n\u003cp\u003e最后我把更新流程固定为：拉取代码、检查配置、构建新镜像、重新创建容器、查看日志。步骤固定之后，部署不再依赖某个人记得哪些命令，也更容易在低配服务器上控制资源。\u003c/p\u003e\n\u003cp\u003e这个实践让我意识到，轻量化不只是少装软件，更是让每个组件的职责足够单一。服务越小，备份、迁移和排错都越容易。\u003c/p\u003e","title":"从脚本到服务：用 Docker Compose 管理一个小项目"},{"content":"三巧小站是一个个人技术博客，主要记录开发、服务器运维和轻量工具方面的实践。内容以技术笔记和经验整理为主，方便日后检索，也希望能给遇到类似问题的人提供一点参考。\n站点保持简洁，专注于文章阅读与技术内容分享。\n如需联系站长，可在后续补充公开邮箱。\n","permalink":"https://3qiao.vip/about/","summary":"\u003cp\u003e三巧小站是一个个人技术博客，主要记录开发、服务器运维和轻量工具方面的实践。内容以技术笔记和经验整理为主，方便日后检索，也希望能给遇到类似问题的人提供一点参考。\u003c/p\u003e\n\u003cp\u003e站点保持简洁，专注于文章阅读与技术内容分享。\u003c/p\u003e\n\u003cp\u003e如需联系站长，可在后续补充公开邮箱。\u003c/p\u003e","title":"关于三巧小站"}]