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

导致此问题的常见错误

  1. Using interactive login: docker login ghcr.io ❌ (Breaks in CI/CD)

  2. 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.

  3. Using wrong image name or tag: Make sure the image and tag exist in GHCR.

  4. Missing package permissions: Your token must have read:packages.

  5. 使用交互式登录: docker login ghcr.io ❌(在 CI/CD 中会中断)

  6. 忘记将环境变量传递给 SSH: 如果不包含 envs: GHCR_TOKEN,GHCR_USERNAME,服务器将接收到空值 → 登录静默失败。

  7. 使用错误的镜像名称或标签: 确保镜像和标签在 GHCR 中存在。

  8. 缺少包权限: 你的 Token 必须具备 read:packages 权限。


Summary

总结

If you remember only one thing: 如果你只需要记住一件事:

GHCR pull failures in CI/CD = ALWAYS authentication problemCI/CD 中的 GHCR 拉取失败 = 永远是身份验证问题

And: 并且:

  • unauthorized → no valid login

  • non-TTY login error → you used interactive login incorrectly

  • unauthorized → 没有有效的登录

  • non-TTY login error → 你错误地使用了交互式登录