GitHub Actions에서 Railway SQLite 데이터베이스를 쿼리하는 방법
Source: Dev.to
왜 SSH인가?
Railway은 SQLite 데이터베이스를 인터넷에 노출하지 않으므로, 데이터베이스 파일은 배포된 컨테이너 내부 디스크에 존재합니다. SSH가 유일하게 접근할 수 있는 방법입니다.
railway ssh "node -e \"const db = require('better-sqlite3')('./data/my-demo.db'); const all = db.prepare('SELECT * FROM signups ORDER BY created_at DESC').all(); console.log(JSON.stringify(all)); db.close();\""
문제 1: 올바른 토큰 얻기
RAILWAY_TOKEN은 프로젝트 토큰이어야 하며, 계정 토큰이 아니어야 합니다.
- Railway 프로젝트 대시보드로 이동합니다.
- Settings → Tokens 로 이동합니다.
- Generate Token 을 클릭합니다.
- 토큰을
RAILWAY_TOKEN이라는 이름의 GitHub Actions 비밀 변수로 추가합니다.
문제 2: 인터랙티브 로그인은 CI에서 작동하지 않음
railway login --browserless 를 실행하면 여전히 수동으로 토큰을 붙여넣어야 하므로 실패합니다.
해결 방법: 로그인 명령을 모두 제거합니다. Railway CLI는 RAILWAY_TOKEN 환경 변수가 설정되어 있으면 자동으로 이를 사용합니다.
문제 3: 서비스 이름 vs. 서비스 ID
CI에서 --project 나 서비스 ID를 사용하면 CLI가 RAILWAY_TOKEN을 무시하고 인터랙티브 로그인을 요구합니다.
해결 방법: --service와 함께 친숙한 서비스 이름만 사용하고 --project는 생략합니다.
railway ssh --service your-service-name --environment production \
"node -e \"const db = require('better-sqlite3')('./path/to/database.db'); const rows = db.prepare('SELECT * FROM your_table').all(); console.log(JSON.stringify(rows)); db.close();\""
문제 4: 따옴표 이스케이프
쉘 → GitHub Actions → YAML 여러 단계의 인용이 명령을 깨뜨렸습니다.
해결 방법: 로직을 별도의 TypeScript 스크립트로 옮기고 워크플로우에서 호출합니다.
// scripts/query-railway.ts
import { execSync } from 'child_process';
interface Record {
id: number;
email: string;
created_at: string;
}
function queryRailway(): Record[] {
const serviceName = process.env.RAILWAY_SERVICE_NAME || 'my-app-service';
const environment = process.env.RAILWAY_ENVIRONMENT || 'production';
const token = process.env.RAILWAY_TOKEN;
if (!token) {
console.error('RAILWAY_TOKEN not set');
process.exit(1);
}
const command = `railway ssh --service ${serviceName} --environment ${environment} "node -e \\"const db = require('better-sqlite3')('./data/database.db'); const rows = db.prepare('SELECT * FROM users').all(); console.log(JSON.stringify(rows)); db.close();\\""`;
try {
const output = execSync(command, { encoding: 'utf-8' });
const lines = output.trim().split('\n');
for (const line of lines) {
if (line.trim().startsWith('[') || line.trim().startsWith('{')) {
return JSON.parse(line.trim());
}
}
console.error('Output from Railway:', output);
throw new Error('Could not find JSON output from Railway');
} catch (error) {
console.error('Failed to query Railway:', error);
process.exit(1);
}
}
const records = queryRailway();
console.log(`Successfully fetched ${records.length} records`);
스크립트를 실행하는 워크플로우 단계
- name: Query db
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
RAILWAY_SERVICE_NAME: my-app-service
RAILWAY_ENVIRONMENT: production
run: npx tsx scripts/query-railway.ts
전체 워크플로우
name: My workflow
on:
workflow_dispatch:
schedule:
- cron: '0 */6 * * *'
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Install Railway CLI
run: npm install -g @railway/cli
- name: Query db
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
RAILWAY_SERVICE_NAME: my-app-service
RAILWAY_ENVIRONMENT: production
run: npx tsx scripts/query-railway.ts