项目背景与最终目标
这次 CI/CD 实验没有直接从复杂项目开始,而是先做了一个很小的静态网页,用它把 Git、GitHub、GitHub Actions、SSH、Docker、Docker Compose 和 Linux VPS 串起来。
这样做的好处是,每一层出了问题都比较容易定位。网页本身没有复杂业务逻辑,部署失败时基本可以判断是 Git、Actions、SSH、Docker、Compose 或服务器环境的问题,而不是应用代码本身。
本次项目的本地目录是:
text
E:\ci-cd-demo
最终项目结构:
text
ci-cd-demo/
├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── ssh-test.yml
├── frontend/
│ ├── index.html
│ ├── style.css
│ └── script.js
├── Dockerfile
└── docker-compose.yml
其中:
frontend/保存网页代码。Dockerfile负责说明 Docker 镜像如何构建。docker-compose.yml负责说明容器如何运行。.github/workflows/ci.yml负责 CI。.github/workflows/ssh-test.yml后来改成了 CD 工作流,负责自动连接 VPS 并部署。
整个流程最终形成:
text
本地代码
│
│ git push
▼
GitHub
│
│ push 触发
▼
GitHub Actions
│
├── CI:检查文件
├── CI:构建 Docker 镜像
│
└── CD:SSH 登录 VPS
│
▼
git pull origin main
│
▼
docker compose up -d --build
│
▼
Docker 容器
│
▼
VPS:80
│
▼
网页
这里需要区分 CI 和 CD。
CI(Continuous Integration,持续集成)主要解决的是"代码提交以后有没有问题"。本次实验中,CI 会检查项目文件是否存在,并尝试构建 Docker 镜像。
CD(Continuous Deployment,持续部署)解决的是"代码确认没有明显问题以后,怎么自动更新到服务器"。本次实验中,CD 通过 GitHub Actions 使用 SSH 连接 VPS,然后在服务器上拉取最新代码并重新构建、启动 Docker Compose。
因此,单独执行 git push 只是把代码提交到了 GitHub;真正形成 CI/CD,是因为 GitHub Actions 接收到 push 事件以后继续自动执行检查和部署。
本地项目与 Docker 部署
先准备一个最小可运行项目
最开始没有直接做后端服务,而是使用 Nginx 提供静态网页。
frontend/index.html 最初是一个简单页面:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CI/CD Demo</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>CI/CD Demo</h1>
<p>这是我的第一个自动部署项目。</p>
<button onclick="sayHello()">测试按钮</button>
<p id="message"></p>
</div>
<script src="script.js"></script>
</body>
</html>
style.css 和 script.js 分别负责页面样式和按钮交互。
最初的网页比较简单,后面为了让部署后的页面更像一个正式的 Demo,又把前端改成了一个深色科技风的 CI/CD 展示页面,增加了自动部署状态、Pipeline、Docker 等内容。
这里有一个值得注意的地方:网页怎么改并不影响 CI/CD 的基本流程。只要代码进入 Git 仓库,后面的自动化过程都可以保持不变。
编写 Dockerfile
项目根目录下创建 Dockerfile:
dockerfile
FROM nginx:alpine
WORKDIR /usr/share/nginx/html
COPY frontend/ .
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
这个 Dockerfile 虽然很短,但已经包含了一个完整 Docker 镜像的基本构建过程。
text
FROM nginx:alpine
表示以 nginx:alpine 作为基础镜像。Alpine 版本比较小,适合这种简单实验。
text
WORKDIR /usr/share/nginx/html
设置工作目录。Nginx 默认会从这个目录提供静态文件。
text
COPY frontend/ .
把项目中的 frontend/ 目录复制到 Nginx 的网页目录。
text
EXPOSE 80
说明容器中的应用使用 80 端口。它更多是一个镜像层面的声明,并不会自动让宿主机开放 80 端口。
text
CMD ["nginx", "-g", "daemon off;"]
让 Nginx 在前台运行。Docker 容器需要有一个持续运行的前台进程,否则容器会退出。
本地第一次构建和运行
先进入项目目录:
powershell
cd E:\ci-cd-demo
构建镜像:
powershell
docker build -t ci-cd-demo:v1 .
这里:
docker build表示构建镜像。-t ci-cd-demo:v1给镜像设置名称和标签。.表示使用当前目录作为 Docker build context。
构建成功以后,可以查看镜像:
powershell
docker images
然后使用 docker run 启动:
powershell
docker run -d --name ci-cd-demo -p 8080:80 ci-cd-demo:v1
这里的:
text
8080:80
含义是:
text
Windows 主机 8080
↓
Docker 容器 80
访问:
text
http://localhost:8080
可以看到网页。
这一步主要是为了先证明 Dockerfile 本身能够正常构建和运行。
从 docker run 过渡到 Docker Compose
手动 docker run 可以运行容器,但参数比较容易越来越多。后面改成使用 Compose 管理。
docker-compose.yml:
yaml
services:
web:
build:
context: .
dockerfile: Dockerfile
container_name: ci-cd-demo
ports:
- "8080:80"
restart: unless-stopped
这里没有使用 version: 字段,因为新版 Docker Compose 已经不需要这个字段。
主要配置:
yaml
build:
context: .
dockerfile: Dockerfile
表示 Compose 不直接拉一个现成应用镜像,而是根据当前目录中的 Dockerfile 构建。
yaml
container_name: ci-cd-demo
指定容器名称,方便后面排查。
yaml
ports:
- "8080:80"
把宿主机 8080 映射到容器 80。
yaml
restart: unless-stopped
让 Docker 在容器异常退出或 Docker 服务重启后自动尝试恢复容器,除非人为停止。
使用 Compose 启动:
powershell
docker compose up -d --build
其中:
up:创建并启动服务。-d:后台运行。--build:启动前重新构建镜像。
查看运行状态:
powershell
docker compose ps
到这里,本地的 Docker + Compose 部分已经验证完成。
Git 与 GitHub 仓库
Docker 能正常运行以后,再把项目纳入 Git 管理。
进入项目:
powershell
cd E:\ci-cd-demo
初始化 Git:
powershell
git init
查看状态:
powershell
git status
第一次提交:
powershell
git add .
git commit -m "Initial CI/CD demo"
然后把主分支统一命名为 main:
powershell
git branch -M main
接下来在 GitHub 创建仓库,然后添加远程仓库。
本次仓库使用 SSH 地址:
text
git@github.com:myvps/ci-cd-demo.git
添加:
powershell
git remote add origin git@github.com:myvps/ci-cd-demo.git
第一次推送:
powershell
git push -u origin main
成功以后,本地代码就进入 GitHub。
这一阶段的关键认识是:Git 和 GitHub 是两个不同的概念。
Git 负责本地版本管理、提交、分支等操作;GitHub 是远程 Git 仓库,同时还提供 GitHub Actions 等自动化能力。
后面 CI/CD 能自动运行,是因为代码被 push 到 GitHub 以后,GitHub Actions 可以监听这个事件。
CI:使用 GitHub Actions 自动检查项目
项目进入 GitHub 后,开始配置 CI。
在:
text
.github/workflows/ci.yml
中写入:
yaml
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check project files
run: |
test -f Dockerfile
test -f docker-compose.yml
test -f frontend/index.html
test -f frontend/style.css
test -f frontend/script.js
- name: Build Docker image
run: docker build -t ci-cd-demo:test .
这份配置可以拆开理解。
yaml
on:
push:
branches:
- main
表示 main 分支发生 push 时触发。
yaml
pull_request:
branches:
- main
表示针对 main 分支的 Pull Request 也可以触发 CI。
yaml
runs-on: ubuntu-latest
表示这个 Job 运行在 GitHub 提供的 Ubuntu Runner 上。
第一步:
yaml
uses: actions/checkout@v4
把当前 GitHub 仓库的代码下载到 Runner。
第二步:
bash
test -f Dockerfile
test -f docker-compose.yml
...
检查关键文件是否存在。
这不是复杂的自动化测试,但对于当前 Demo 很合适,因为首先需要确认项目结构没有被破坏。
第三步:
bash
docker build -t ci-cd-demo:test .
尝试构建 Docker 镜像。
这一点很重要,因为 CI 不只是检查 Git 文件是否存在,还提前验证 Dockerfile 能不能构建。
整个 CI 的意义可以理解为:
text
开发者 push
↓
GitHub Actions
↓
拉取代码
↓
检查项目文件
↓
构建 Docker 镜像
↓
成功 / 失败
如果 CI 失败,就应该先解决问题,而不是直接认为代码可以部署。
SSH:让 GitHub Actions 和 VPS 能互相完成工作
真正做 CD 之前,需要解决两个不同方向的 SSH 问题。
这次特意使用了两套 SSH Key,因为它们解决的是完全不同的事情。
GitHub Actions → VPS
GitHub Actions 需要主动登录 VPS,所以在 VPS 上生成了一套专门给 GitHub Actions 使用的密钥:
text
/root/.ssh/github_actions
/root/.ssh/github_actions.pub
公钥加入:
text
/root/.ssh/authorized_keys
私钥则保存到 GitHub 仓库的 Actions Secrets 中。
配置了:
text
VPS_HOST
VPS_USER
VPS_SSH_KEY
其中:
VPS_HOST:VPS 公网 IP。VPS_USER:登录用户,本次使用root。VPS_SSH_KEY:GitHub Actions 登录 VPS 使用的私钥。
之后使用 appleboy/ssh-action@v1 测试。
测试成功时日志中出现了:
text
Drone SSH version 1.8.2
Docker Compose version v5.5.0
✅ Successfully executed commands to all hosts.
这一步非常重要,因为它证明:
text
GitHub Actions
↓ SSH
VPS
这条链路已经打通。
VPS → GitHub
但是 CD 还有另外一个方向。
GitHub Actions 登录 VPS 后,VPS 需要执行:
bash
git pull origin main
因此 VPS 自己也需要能够访问 GitHub。
一开始直接:
bash
git clone git@github.com:myvps/ci-cd-demo.git
出现:
text
Permission denied (publickey)
这不是 GitHub Actions 的 Key 有问题,而是因为 VPS → GitHub 这一方向没有认证。
于是又在 VPS 上生成了一套独立的密钥:
text
/root/.ssh/github
/root/.ssh/github.pub
把 /root/.ssh/github.pub 添加到 GitHub 账户的 SSH and GPG keys 中。
然后测试:
bash
ssh -i /root/.ssh/github -o IdentitiesOnly=yes -T git@github.com
成功返回:
text
Hi myvps! You've successfully authenticated, but GitHub does not provide shell access.
这句话的含义不是失败。
GitHub 本身不提供普通 SSH Shell,但已经确认 GitHub 成功识别了这把 SSH Key。
为了以后不需要每次手动指定:
bash
-i /root/.ssh/github
又配置了:
text
/root/.ssh/config
内容:
text
Host github.com
HostName github.com
User git
IdentityFile /root/.ssh/github
IdentitiesOnly yes
然后:
bash
chmod 600 /root/.ssh/config
这样 VPS 以后执行:
bash
git pull origin main
就可以自动使用这把 Key。
在 VPS 上准备项目目录
最终把项目放在:
text
/opt/ci-cd-demo
也就是说,服务器上的工作目录大致是:
text
/opt/ci-cd-demo
├── .github/
├── frontend/
├── Dockerfile
└── docker-compose.yml
这里的逻辑是:
text
GitHub
↑
│ git pull
│
VPS /opt/ci-cd-demo
GitHub Actions 本身并不需要把整个项目文件通过 SSH 传过去。
它只需要:
- SSH 登录 VPS。
- 进入项目目录。
git pull获取最新代码。- 使用最新代码重新构建并启动容器。
CD:把部署真正自动化
前面的 CI 和 SSH 都验证以后,开始配置 CD。
原本用于 SSH 测试的:
text
.github/workflows/ssh-test.yml
后来直接改成真正的 CD 工作流:
yaml
name: CD
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to VPS
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd /opt/ci-cd-demo
echo "Pull latest code..."
git pull origin main
echo "Build and restart..."
docker compose up -d --build
echo "Deployment completed!"
echo "Container status:"
docker compose ps
这里的执行过程非常清楚。
第一步:push 触发
yaml
on:
push:
branches:
- main
只要代码 push 到 main,CD 就会开始。
第二步:GitHub Actions 登录 VPS
yaml
uses: appleboy/ssh-action@v1
使用前面配置好的:
text
VPS_HOST
VPS_USER
VPS_SSH_KEY
建立 SSH 连接。
第三步:进入服务器项目目录
bash
cd /opt/ci-cd-demo
第四步:拉取最新代码
bash
git pull origin main
这一条命令把 GitHub 上刚刚 push 的代码同步到服务器。
实际测试时出现过一次:
text
Updating 4f490d7..fa136ad
Fast-forward
说明 VPS 成功从 GitHub 拉到了新的提交。
第五步:重新构建并启动
bash
docker compose up -d --build
因为网页代码发生变化,所以需要重新构建镜像。
完整过程:
text
GitHub 新代码
↓
git pull
↓
Dockerfile
↓
重新 build
↓
生成新镜像
↓
重新创建 / 启动容器
第六步:查看容器
bash
docker compose ps
用于确认服务是否正常运行。
第一次真正部署时遇到的问题:8080 端口冲突
第一次 CD 部署时,GitHub Actions、SSH、Git Pull、Docker Build 都成功了,但是容器启动失败:
text
Bind for 0.0.0.0:8080 failed: port is already allocated
这说明问题已经不是 GitHub、SSH 或 Dockerfile,而是 VPS 上已经有其他程序占用了 8080。
当时没有直接执行停止命令,而是先要求检查:
bash
docker ps --format "table {{.Names}}\t{{.Ports}}"
以及:
bash
ss -lntp | grep ':8080'
原因是 VPS 上还有其他正在运行的服务,不能为了部署 Demo 就随便停止某个容器。
最终决定把 Demo 的宿主机端口改成 80。
Compose 改成:
yaml
services:
web:
build:
context: .
dockerfile: Dockerfile
container_name: ci-cd-demo
ports:
- "80:80"
restart: unless-stopped
这里仍然是:
text
宿主机 80 → 容器 80
修改后再次:
powershell
git add docker-compose.yml
git commit -m "Change web port to 80"
git push
GitHub Actions 再次触发 CD,最终部署成功。
这个问题很值得记录下来,因为实际工作中部署失败并不一定是代码问题。端口被占用、磁盘不足、权限错误、Docker 网络问题等,都可能导致部署失败。
一个容易忽略的问题:部署失败时 Workflow 可能仍显示成功
第一次部署时还发现了一个脚本层面的隐患。
虽然:
bash
docker compose up -d --build
已经报错,但是后面的:
bash
echo "Deployment completed!"
docker compose ps
仍然继续执行。
因此 GitHub Actions 最终可能把整个 SSH Step 当成成功。
这说明当前 Workflow 还不够严谨。
后续应该在脚本开头增加:
bash
set -e
例如:
yaml
script: |
set -e
cd /opt/ci-cd-demo
echo "Pull latest code..."
git pull origin main
echo "Build and restart..."
docker compose up -d --build
echo "Deployment completed!"
echo "Container status:"
docker compose ps
这样只要其中一个命令返回非 0 状态,脚本就会停止,GitHub Actions 才能正确显示部署失败。
这也是这次实验中比较重要的一点:自动化不是把几个命令串起来就结束了,还需要保证失败能够被正确传递。
最终验证自动部署
CD 成功以后,最重要的不是只看 GitHub Actions 绿色,而是验证完整闭环。
下一次修改网页内容,例如修改:
html
<h1>CI/CD Demo</h1>
为:
html
<h1>My CI/CD Project</h1>
然后本地:
powershell
git add .
git commit -m "Update homepage"
git push
这时候不再登录 VPS 手动执行:
bash
git pull
也不再手动执行:
bash
docker compose up -d --build
而是直接等待:
text
git push
↓
GitHub
↓
GitHub Actions
↓
CI
↓
CD
↓
SSH
↓
git pull
↓
Docker Compose
↓
网页更新
然后访问服务器的 80 端口,确认页面已经发生变化。
到这里,整个实验才真正完成了"自动部署"的验证。
最终流程、问题记录与后续改进
这次实验最终形成的基础 CI/CD 架构可以概括为:
text
┌──────────────────┐
│ Windows 本地开发 │
│ │
│ 修改 HTML/CSS/JS │
└────────┬─────────┘
│
│ git push
▼
┌──────────────────┐
│ GitHub │
│ main 分支 │
└────────┬─────────┘
│
│ push trigger
▼
┌────────────────────────────┐
│ GitHub Actions │
│ │
│ CI: │
│ 1. checkout │
│ 2. 检查项目文件 │
│ 3. docker build │
│ │
│ CD: │
│ 4. SSH 登录 VPS │
└────────┬───────────────────┘
│
│ SSH
▼
┌────────────────────────────┐
│ Ubuntu VPS │
│ │
│ /opt/ci-cd-demo │
│ │ │
│ git pull │
│ ↓ │
│ docker compose │
│ ↓ │
│ Docker Container │
│ ↓ │
│ nginx:80 │
└────────────────────────────┘
整个实验过程中实际遇到的问题主要集中在以下几个方面。
Docker 与服务器环境问题
Dockerfile 本身可以构建,但服务器上的 Docker 环境和本地并不完全一样。
尤其是服务器拉取:
text
nginx:alpine
速度比较慢,所以 Docker Build 在 VPS 上花费的时间明显比本地长。
这说明"本地能跑"并不代表"服务器一定能快速部署"。实际项目还需要考虑镜像缓存、镜像仓库、网络环境等问题。
SSH 是两个方向的问题
这次最容易混淆的地方之一就是两套 SSH Key。
实际关系是:
text
github_actions
GitHub Actions → VPS
以及:
text
github
VPS → GitHub
两者不能混为一谈。
前者解决:
text
Actions 怎么登录服务器?
后者解决:
text
服务器怎么从 GitHub 拉代码?
这两个方向都打通以后,CD 才能顺利执行。
GitHub Actions Secret 的作用
VPS 地址、登录用户和私钥没有直接写到公开的 Workflow 中,而是使用:
text
${{ secrets.VPS_HOST }}
${{ secrets.VPS_USER }}
${{ secrets.VPS_SSH_KEY }}
这样可以避免把 SSH 私钥直接写进 Git 仓库。
特别是 SSH 私钥、Token、密码等内容,不能提交到 Git。
如果密钥意外暴露,应当及时更换,而不是继续使用已经泄露的凭证。
端口冲突的排查思路
第一次部署失败时:
text
Bind for 0.0.0.0:8080 failed:
port is already allocated
正确的思路不是直接执行:
bash
kill
或者随便:
bash
docker stop
而是先确认是谁占用了端口:
bash
docker ps --format "table {{.Names}}\t{{.Ports}}"
bash
ss -lntp | grep ':8080'
确认之后,再决定是释放端口,还是修改项目端口。
对于一台已经运行多个服务的 VPS,这一点尤其重要。
当前方案为什么适合学习,但还不算最完善
现在的 CD 是:
text
GitHub
↓
SSH
↓
VPS git pull
↓
VPS docker build
↓
VPS docker compose up
它的优点是简单,能够把 CI/CD 的核心流程完整跑通。
但它也有一个比较明显的问题:每次部署都要在 VPS 上重新构建镜像,而且 VPS 还需要访问 Docker Hub 拉取基础镜像。
更进一步的做法可以变成:
text
GitHub
↓
GitHub Actions
↓
构建 Docker 镜像
↓
推送到 GHCR 等镜像仓库
↓
VPS docker pull
↓
docker compose up
这样 VPS 主要负责运行容器,不负责完整的镜像构建。
不过对于第一次学习 CI/CD 来说,目前这种:
text
git pull
+
docker compose up -d --build
反而更容易理解每一步到底发生了什么,因此适合作为第一版。
这次实验真正需要掌握的并不是某一条命令,而是整个部署思路:
text
代码管理 → 自动检查 → 自动连接服务器 → 拉取代码
→ 构建镜像 → 启动容器 → 验证服务
以后换成 FastAPI、Node.js、Java、前后端分离项目,甚至更复杂的服务,只是 Dockerfile、Compose 和测试命令发生变化,整个 CI/CD 的基本思想仍然一样。
补充:密钥生成和部分步骤
sh
root@myvps:~# mkdir -p /root/.ssh
root@myvps:~# chmod 700 /root/.ssh
root@myvps:~# ssh-keygen -t ed25519 -C "github-actions-ci-cd"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/root/.ssh/id_ed25519): /root/.ssh/github_actions
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/github_actions
Your public key has been saved in /root/.ssh/github_actions.pub
The key fingerprint is:
SHA256:6gxYoxCAHTJVhq0P4RqpYJHu46PtOC4L0Yx869AAxek github-actions-ci-cd
The key's randomart image is:
+--[ED25519 256]--+
|=o+=o ... |
|o=+.o .o |
|oo.o o. |
|=C+ .D |
|C+o+o. .Q |
|o=.+=... |
|o + .o. |
|+= + |
|M=+ o |
+----[SHA256]-----+
root@myvps:~# cat /root/.ssh/github_actions
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAAd7RigAAAJjlyI/75ciP
+wAAlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAXQtIgqkZnpsMfdd7Rig
AAlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAAj+waEplEsMARiZIA3JO
t5dlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAApLWNkAQ==
-----END OPENSSH PRIVATE KEY-----
root@myvps:~# cat /root/.ssh/github_actions.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGL1rj+AAEbm9uZQAAAAAAAAAd7RigAAAJjlyI/75ciP github-actions-ci-cd
root@myvps:~# cat /root/.ssh/github_actions.pub >> /root/.ssh/authorized_keys
root@myvps:~# chmod 600 /root/.ssh/authorized_keys
root@myvps:~# ls -l /root/.ssh/github_actions*
-rw------- 1 root root 411 Sep 11 12:41 /root/.ssh/github_actions
-rw-r--r-- 1 root root 102 Sep 11 12:41 /root/.ssh/github_actions.pub
root@myvps:~# ls
docker-pull.log virt-sysprep-firstboot.log
root@myvps:~# cd /opt/
root@myvps:/opt# ls
3x-ui containerd new-api sub2api vllm
root@myvps:/opt# git clone git@github.com:myvps/ci-cd-demo.git
Cloning into 'ci-cd-demo'...
The authenticity of host 'github.com (20.205.243.166)' can't be established.
ED25519 key fingerprint is SHA256:+DiY3wvvV6TuJJhbpCasF/zLDA0zPMSvHdkr4UvAdqU.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? y
Please type 'yes', 'no' or the fingerprint: yes
Warning: Permanently added 'github.com' (ED25519) to the list of known hosts.
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
root@myvps:/opt# git clone git@github.com:myvps/ci-cd-demo.git
Cloning into 'ci-cd-demo'...
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
root@myvps:/opt# ssh-keygen -t ed25519 -C "vps-github"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/root/.ssh/id_ed25519): /root/.ssh/github
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/github
Your public key has been saved in /root/.ssh/github.pub
The key fingerprint is:
SHA256:bYldbnQGvLfQzYDISdHPrPHJCG/4Aa1rF+beg/8Nc10 vps-github
The key's randomart image is:
+--[ED25519 256]--+
| ooB.o |
| * *.+ |
| *o@o= |
| +o=@oD o|
| A =*o= . |
| .o.+ .. |
| . o.oo .|
| oooo.|
| .Eoo|
+----[SHA256]-----+
root@myvps:/opt# cat /root/.ssh/github.pub
ssh-ed25519 AAAAC3NzaC1lQDI1NTE5AAAAIFi+7RMlJxc5gSjSCx8ncgQYNMMwEZY5E8078FK8f0Y1 vps-github
root@myvps:/opt# ssh -T git@github.com
git@github.com: Permission denied (publickey).
root@myvps:/opt# ssh -T git@github.com
git@github.com: Permission denied (publickey).
root@myvps:/opt# ls -l /root/.ssh/github*
-rw------- 1 root root 399 Sep 11 12:54 /root/.ssh/github
-rw-r--r-- 1 root root 92 Sep 11 12:54 /root/.ssh/github.pub
-rw------- 1 root root 411 Sep 11 12:41 /root/.ssh/github_actions
-rw-r--r-- 1 root root 102 Sep 11 12:41 /root/.ssh/github_actions.pub
root@myvps:/opt# ssh-keygen -lf /root/.ssh/github.pub
256 SHA256:bYldbnQGvLfQzYDDiwLPrPHJCG/4Aa1rF+beg/8Ncq0 vps-github (ED25519)
root@myvps:/opt# ssh -i /root/.ssh/github -o IdentitiesOnly=yes -T git@github.com
Hi myvps! You've successfully authenticated, but GitHub does not provide shell access.
root@myvps:/opt# cat > /root/.ssh/config <<'EOF'
Host github.com
HostName github.com
User git
IdentityFile /root/.ssh/github
IdentitiesOnly yes
EOF
root@myvps:/opt# chmod 600 /root/.ssh/config
root@myvps:/opt# ssh -T git@github.com
Hi myvps! You've successfully authenticated, but GitHub does not provide shell access.
root@myvps:/opt# cd /opt
root@myvps:/opt# git clone git@github.com:myvps/ci-cd-demo.git
Cloning into 'ci-cd-demo'...
remote: Enumerating objects: 18, done.
remote: Counting objects: 100% (18/18), done.
remote: Compressing objects: 100% (14/14), done.
remote: Total 18 (delta 2), reused 17 (delta 1), pack-reused 0 (from 0)
Receiving objects: 100% (18/18), done.
Resolving deltas: 100% (2/2), done.
root@myvps:/opt# ls
ci-cd-demo containerd vllm
root@myvps:/opt# cd ci-cd-demo/
root@myvps:/opt/ci-cd-demo# ls
Dockerfile docker-compose.yml frontend
root@myvps:/opt/ci-cd-demo# git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
root@myvps:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
52642a98e30b ci-cd-demo-web "/docker-entrypoint...." About a minute ago Up About a minute 0.0.0.0:80->80/tcp, [::]:80->80/tcp ci-cd-demo