Fixing GHCR “Unauthorized” + Docker “Cannot perform interactive login from non-TTY” in GitHub Actions + SSH Deployments
Fixing GHCR “Unauthorized” + Docker “Cannot perform interactive login from non-TTY” in GitHub Actions + SSH Deployments
修复 GitHub Actions + SSH 部署中遇到的 GHCR “Unauthorized” 及 Docker “Cannot perform interactive login from non-TTY” 错误
When deploying applications using GitHub Actions + SSH (appleboy/ssh-action) and pulling images from GitHub Container Registry (GHCR), two common errors often appear together: 在使用 GitHub Actions + SSH (appleboy/ssh-action) 部署应用并从 GitHub Container Registry (GHCR) 拉取镜像时,通常会同时出现以下两个错误:
-
unauthorized when pulling Docker images from GHCR
-
Cannot perform an interactive login from a non TTY device
-
从 GHCR 拉取 Docker 镜像时提示 unauthorized(未授权)
-
提示 Cannot perform an interactive login from a non TTY device(无法在非 TTY 设备上执行交互式登录)
These errors usually look confusing, but they both come from the same root issue: Docker authentication is not correctly handled in a non-interactive environment (CI/CD or SSH automation). This article explains what is happening, why it fails, and how to fix it permanently. 这些错误看起来令人困惑,但它们都源于同一个根本问题:Docker 身份验证在非交互式环境(CI/CD 或 SSH 自动化)中处理不当。本文将解释其发生的原因、失败的根源以及如何彻底解决。
The Problem (What You See in Logs)
问题表现(日志中看到的内容)
1. GHCR Unauthorized Error 1. GHCR 未授权错误
Error response from daemon: Head "https://ghcr.io/v2/ORG/REPO/manifests/latest": unauthorized
This means Docker tried to pull a private image from GHCR but was not authenticated. 这意味着 Docker 尝试从 GHCR 拉取私有镜像,但未通过身份验证。
2. Non-TTY Login Error 2. 非 TTY 登录错误
Error: Cannot perform an interactive login from a non TTY device
This means Docker tried to run: docker login ghcr.io but failed because: GitHub Actions and SSH scripts do not support interactive input (no terminal to type username/password).
这意味着 Docker 尝试执行 docker login ghcr.io,但失败了,原因是:GitHub Actions 和 SSH 脚本不支持交互式输入(没有终端供你输入用户名/密码)。
Root Cause
根本原因
Both errors come from the same issue: Docker login is either: 这两个错误都源于同一个问题:Docker 登录操作要么:
-
Missing entirely ❌
-
Or written in interactive mode ❌
-
Or not receiving credentials correctly ❌
-
完全缺失 ❌
-
或者以交互模式编写 ❌
-
或者凭据传递不正确 ❌
In CI/CD environments: There is no keyboard input, there is no terminal (TTY). Everything must be fully automated. 在 CI/CD 环境中:没有键盘输入,也没有终端 (TTY)。一切都必须完全自动化。
Correct Mental Model
正确的思维模型
Think of GHCR like a private building: 把 GHCR 想象成一栋私人大楼:
-
docker pull= trying to enter the building -
docker login= showing your ID card -
docker pull= 试图进入大楼 -
docker login= 出示你的身份证件
If you don’t show your ID before entering, you get: ❌ unauthorized 如果你进入前不展示证件,你会得到:❌ unauthorized
If you try to “type your password manually” in automation: ❌ non-TTY error 如果你试图在自动化流程中“手动输入密码”:❌ non-TTY error
Correct Fix (Production-Ready Solution)
正确的修复方法(生产环境方案)
Step 1: Create a GitHub Token (PAT) 第一步:创建 GitHub Token (PAT)
Go to: GitHub → Settings → Developer Settings → Personal Access Tokens 前往:GitHub → Settings → Developer Settings → Personal Access Tokens
Create a token with: 创建一个包含以下权限的 Token:
-
read:packages✅ -
repo(if private repo) ✅ -
read:packages✅ -
repo(如果是私有仓库) ✅
Step 2: Store Secrets in GitHub Actions 第二步:在 GitHub Actions 中存储 Secrets
Add these secrets: 添加以下 Secrets:
-
GHCR_USERNAME→ your GitHub username -
GHCR_TOKEN→ your personal access token -
GHCR_USERNAME→ 你的 GitHub 用户名 -
GHCR_TOKEN→ 你的个人访问令牌
Step 3: Pass Secrets into SSH Action 第三步:将 Secrets 传递给 SSH Action
In your GitHub Actions workflow: 在你的 GitHub Actions 工作流中:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.2.5
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USER }}
key: ${{ secrets.SSH_KEY }}
envs: GHCR_USERNAME,GHCR_TOKEN
script: |
echo $GHCR_TOKEN | docker login ghcr.io -u $GHCR_USERNAME --password-stdin
docker compose pull
docker compose up -d
Step 4: Use Non-Interactive Docker Login (IMPORTANT) 第四步:使用非交互式 Docker 登录(重要)
-
✔️ Correct way:
echo $GHCR_TOKEN | docker login ghcr.io -u $GHCR_USERNAME --password-stdin -
❌ Wrong way (causes TTY error):
docker login ghcr.io -
✔️ 正确方式:
echo $GHCR_TOKEN | docker login ghcr.io -u $GHCR_USERNAME --password-stdin -
❌ 错误方式(会导致 TTY 错误):
docker login ghcr.io
Step 5: Verify Login on Server 第五步:在服务器上验证登录
Run: cat ~/.docker/config.json
执行:cat ~/.docker/config.json
If login worked, you should see: 如果登录成功,你应该能看到:
"auths": {
"ghcr.io": {
"auth": "..."
}
}
Final Deployment Flow (Correct Order)
最终部署流程(正确顺序)
Your SSH deployment script should always follow this pattern: 你的 SSH 部署脚本应始终遵循此模式:
set -e
echo "Logging into GHCR..."
echo $GHCR_TOKEN | docker login ghcr.io -u $GHCR_USERNAME --password-stdin
echo "Pulling latest images..."
docker compose pull
echo "Restarting containers..."
docker compose up -d
echo "Cleaning unused images..."
docker system prune -f
Common Mistakes That Cause This Issue
导致此问题的常见错误
-
Using interactive login:
docker login ghcr.io❌ (Breaks in CI/CD) -
Forgetting to pass environment variables into SSH: If you don’t include
envs: GHCR_TOKEN,GHCR_USERNAME, the server receives empty values → login fails silently. -
Using wrong image name or tag: Make sure the image and tag exist in GHCR.
-
Missing package permissions: Your token must have
read:packages. -
使用交互式登录:
docker login ghcr.io❌(在 CI/CD 中会中断) -
忘记将环境变量传递给 SSH: 如果不包含
envs: GHCR_TOKEN,GHCR_USERNAME,服务器将接收到空值 → 登录静默失败。 -
使用错误的镜像名称或标签: 确保镜像和标签在 GHCR 中存在。
-
缺少包权限: 你的 Token 必须具备
read:packages权限。
Summary
总结
If you remember only one thing: 如果你只需要记住一件事:
❗ GHCR pull failures in CI/CD = ALWAYS authentication problem ❗ CI/CD 中的 GHCR 拉取失败 = 永远是身份验证问题
And: 并且:
-
unauthorized→ no valid login -
non-TTY login error→ you used interactive login incorrectly -
unauthorized→ 没有有效的登录 -
non-TTY login error→ 你错误地使用了交互式登录