Posted on:September 17, 2026 at 12:00 AM

Infisical 처음부터 끝까지: 셀프 호스팅, CLI, Docker Compose, Kubernetes

Infisical 처음부터 끝까지: 셀프 호스팅, CLI, Docker Compose, Kubernetes

Infisical 처음부터 끝까지: 셀프 호스팅, CLI, Docker Compose, Kubernetes

대부분의 프로젝트는 .env 파일로 시작하고, 대부분 거기서 벗어나지 못합니다. 그 파일은 scp로 서버에 복사되고, 새 팀원이 들어오면 Slack 스레드에 붙여넣어지고, 노트북 세 대에 남아 잊힙니다. 어떤 값이 최신인지 아무도 모르고, 유출된 키를 교체하려면 모든 복사본을 추적해야 합니다.

이 글은 그 방식을 Infisical로 대체하는 과정을 다룹니다. 시크릿이 내 장비에 남도록 셀프 호스팅으로 구성하고, 실제로 시크릿이 소비되는 두 곳 — Docker Compose 호스트와 Kubernetes 클러스터 — 에 연결하는 데까지 갑니다.

글의 흐름:

  1. Infisical이란?
  2. 설치 경로 선택하기
  3. Docker Compose로 셀프 호스팅하기
  4. CLI 설치하기
  5. 로컬 .env로 프로젝트 부트스트랩하기
  6. Compose 서버에서 시크릿 사용하기 — 서버측 .env 없애기
  7. Kubernetes에서 시크릿 사용하기

이 글이 기준으로 삼은 버전: 공개된 docker-compose.prod.yml 기준의 infisical/infisical:latest 서버, 그리고 v1beta1 CRD를 쓰는 Kubernetes 오퍼레이터 Helm 차트 v0.11.9. Infisical은 변화가 빠르니 붙여넣기 전에 공식 문서에서 버전을 확인하세요.


1. Infisical이란?

Infisical은 오픈소스 시크릿 관리 플랫폼입니다. 핵심은 시크릿을 암호화해서 저장하는 서버이고, 여기에 CLI·SDK·Kubernetes 오퍼레이터·CI 연동 같은 클라이언트들이 붙습니다. 이 클라이언트들이 런타임에 시크릿을 가져오기 때문에, 시크릿이 코드 옆 파일에 존재할 이유가 사라집니다.

개념 모델은 3단계 트리입니다.

Organization (조직)
└── Project (프로젝트, 예: "orders-api")
    ├── Environment: dev
    ├── Environment: staging
    └── Environment: prod
        └── Folder path: /   /database   /stripe
            └── SECRET_KEY = value

시크릿 하나는 프로젝트 + 환경 슬러그 + 폴더 경로 + 키로 주소가 정해집니다. 아래에 나오는 모든 클라이언트 — infisical run, 오퍼레이터 CRD, 각종 SDK — 는 결국 이 네 가지 좌표의 변형을 인자로 받습니다.

.env 파일 대비 실제로 얻는 것

.env의 문제Infisical이 해결하는 방식
누가 복사본을 갖고 있는지 알 수 없음프로젝트·환경 단위 접근 제어와 모든 조회에 대한 감사 로그
키 교체가 파일 추적 작업이 됨값을 한 번만 바꾸면, 클라이언트는 다음 조회나 재시작 때 새 값을 사용
시크릿이 git에 올라감커밋할 것이 없음 — 커밋되는 프로젝트 설정 파일에는 값이 들어있지 않음
머신에 파일을 사람이 직접 넣어줘야 함머신 아이덴티티가 클라이언트 ID/시크릿으로 스스로 인증
dev와 prod 값이 서로 어긋남같은 키, 다른 환경 — 한 화면에서 비교 가능

이 외에 인증서 관리, 다이나믹 시크릿(요청 시점에 발급되는 수명이 짧은 DB 자격 증명), 시크릿 스캐닝도 제공합니다. 이 글은 대부분의 팀이 가장 먼저 필요로 하는 정적 시크릿만 다룹니다.

다른 도구와의 비교

  • HashiCorp Vault — 훨씬 강력하지만 운영 부담도 훨씬 큽니다. 이미 Vault를 운영하고 있지 않고 “API 키를 제대로 보관할 곳”이 필요한 정도라면, Vault는 과한 장비입니다.
  • Doppler / AWS Secrets Manager / GCP Secret Manager — 사용 경험은 비슷하지만 모두 호스팅 전용입니다. Infisical의 차별점은 같은 제품을 깔끔하게 셀프 호스팅할 수 있다는 점입니다.
  • SOPS + age — 시크릿을 암호화해서 git에 두고 싶다면 훌륭한 선택입니다. 철학이 다릅니다. SOPS에는 서버도, 감사 로그도, 런타임 API도 없습니다. GitOps에는 잘 맞지만 “지금 당장 이 키를 서비스 12개에 걸쳐 교체하라”에는 맞지 않습니다.

2. 설치 경로 선택하기

결정할 것은 두 가지인데, 실제로 고민할 가치가 있는 건 두 번째뿐입니다.

서버: Infisical Cloud(https://app.infisical.com, 무료 티어 있음)를 쓰거나 직접 호스팅합니다. 이 글의 모든 내용은 둘 다에서 동작하며, 차이는 셀프 호스팅일 때 클라이언트에게 서버 주소를 알려줘야 한다는 것뿐입니다. 3장부터는 셀프 호스팅을 전제로 합니다.

셀프 호스팅 방식:

방식적합한 경우트레이드오프
Docker Compose단일 호스트, 홈랩, 소규모 팀단일 노드, HA 없음 — 문서가 권하는 출발점
Kubernetes (Helm)이미 Kubernetes를 운영 중인 경우구성 요소가 많고 Postgres·Redis는 직접 관리
스탠드얼론 Docker기존 오케스트레이터에 끼워 넣을 때Postgres와 Redis를 직접 제공해야 함

3장에서 다루는 것은 Docker Compose입니다. 여기서 사람들이 자주 헷갈리는 구분을 짚고 갑니다. Compose로 Infisical을 셀프 호스팅하는 것(3장)과 Compose 앱에서 Infisical 시크릿을 가져다 쓰는 것(6장)은 서로 다른 문제이며, Infisical 서버가 그 서버가 서비스하는 앱과 같은 호스트에 있을 필요는 없습니다.

준비물

  • Docker와 Compose 플러그인이 설치된 호스트 (docker compose version이 동작할 것)
  • 여유 메모리 2 GB 정도. 스택은 Node 백엔드, Postgres, Redis로 구성됩니다
  • 내 노트북 외의 무언가가 접속할 거라면 DNS 이름과 TLS

3. Docker Compose로 셀프 호스팅하기

3.1 파일 받기

mkdir -p ~/infisical && cd ~/infisical

curl -o docker-compose.prod.yml \
  https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml

curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example

Compose 파일은 세 개의 서비스 — backend(infisical/infisical:latest), db(postgres:14-alpine), redis — 를 정의하고, 네임드 볼륨 pg_data·redis_data를 사용하며, 백엔드를 80:8080으로 노출합니다.

3.2 진짜 키 생성하기

예제 .env에는 개발 전용이라고 명시된 샘플 값이 들어 있습니다. 다른 무엇보다 먼저 교체하세요.

echo "ENCRYPTION_KEY=$(openssl rand -hex 16)"
echo "AUTH_SECRET=$(openssl rand -base64 32)"

두 개의 키는 역할이 완전히 다릅니다.

변수역할형식
ENCRYPTION_KEYPostgres에 저장되는 모든 시크릿을 암호화16진수 32자 (rand -hex 16)
AUTH_SECRET세션과 JWT 토큰 서명base64, 32바이트

ENCRYPTION_KEY를 지금, 이 호스트 바깥에 백업하세요. 이 키가 없는 Postgres 덤프는 읽을 수 없습니다. 새로 만든 키와 함께 DB를 다른 머신에 복원하면, 동작하는 로그인 페이지와 복구 불가능한 시크릿만 남습니다. 나중에 이 키를 교체하는 것은 문서화된 절차이긴 하지만 신중하게 진행해야 하는 작업이며, 장애 상황에서 즉흥적으로 할 일이 아닙니다.

3.3 나머지 .env 채우기

# --- 필수 ---
ENCRYPTION_KEY=<위에서 만든 16진수 32>
AUTH_SECRET=<위에서 만든 base64 문자>

POSTGRES_USER=infisical
POSTGRES_PASSWORD=<충분히 랜덤 비밀번>
POSTGRES_DB=infisical
DB_CONNECTION_URI=postgres://infisical:<위와 같은 비밀번>@db:5432/infisical

REDIS_URL=redis://redis:6379

# 사용자와 클라이언트가 실제로 접속할 URL. 서버라면 "localhost"가 아니어야 합니다.
SITE_URL=https://secrets.example.com

# --- 선택이지만 권장: 초대와 비밀번호 재설정에 쓰이는 SMTP ---
SMTP_HOST=
SMTP_PORT=
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_FROM_ADDRESS=
SMTP_FROM_NAME=Infisical

처음부터 제대로 맞춰야 할 것 두 가지:

  • DB_CONNECTION_URIPOSTGRES_* 값과 일치해야 합니다. 호스트명은 Compose 서비스 이름인 db이지 localhost가 아닙니다.
  • SITE_URL은 외부에서 접근 가능한 URL이어야 합니다. 이 값이 초대 링크와 이메일에 들어가기 때문에, 잘못 넣으면 아무도 클릭할 수 없는 링크가 발송됩니다.

이제 이 파일은 모든 것의 열쇠를 담고 있으므로 권한을 조입니다.

chmod 600 .env

3.4 실행

docker compose -f docker-compose.prod.yml up -d
docker compose -f docker-compose.prod.yml ps

확인 지점: 컨테이너 세 개가 모두 running이고 db가 healthy로 보고되어야 합니다. backend가 재시작을 반복한다면 거의 항상 DB_CONNECTION_URI 문제입니다.

docker compose -f docker-compose.prod.yml logs -f backend

3.5 첫 로그인

http://<host>/를 엽니다. 백엔드는 80 포트로 노출되어 있습니다. 가장 먼저 가입한 계정이 인스턴스 관리자가 되므로, 스택이 뜨자마자 그리고 호스트가 통제 범위 밖에서 접근 가능해지기 전에 가입을 마치세요.

그다음 조직을 만들고, 첫 프로젝트를 만들고, 프로젝트 설정 페이지에서 프로젝트 ID를 확인해 둡니다. 아래 나오는 모든 머신 클라이언트가 이 값을 필요로 합니다.

3.6 앞단에 TLS 두기

Compose 스택은 평문 HTTP로 통신합니다. 노트북을 넘어서는 용도라면 Caddy, nginx, Cloudflare Tunnel 같은 리버스 프록시로 TLS를 종단하고 호스트의 80 포트로 전달해야 합니다. 그 후 SITE_URLhttps:// 주소로 바꾸고 백엔드를 재시작하세요.


4. CLI 설치하기

CLI는 앞으로 가장 많이 쓰게 될 도구입니다. 프로젝트를 초기화하고, 프로세스에 시크릿을 주입하고, 머신을 인증합니다.

# macOS
brew install infisical/get-cli/infisical

# Debian / Ubuntu
curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | sudo -E bash
sudo apt-get update && sudo apt-get install -y infisical

# RHEL / CentOS / Amazon Linux
curl -1sLf 'https://artifacts-cli.infisical.com/setup.rpm.sh' | sudo -E bash
sudo yum install infisical

# Node가 있는 모든 플랫폼
npm install -g @infisical/cli
infisical --version

서버와 CI에서는 latest를 따라가지 말고 버전을 고정하세요. 재설치할 때 동작이 조용히 바뀌는 일을 막아줍니다.

CLI가 내 서버를 바라보게 하기

셀프 호스팅에서 가장 흔하게 걸려 넘어지는 지점입니다. CLI의 기본 대상은 Infisical Cloud입니다. 이 단계를 건너뛰면 infisical login은 아무 불평 없이 app.infisical.com에 인증한 뒤, 내 프로젝트가 존재하지 않는다고 알려줍니다.

우선순위가 높은 순서대로:

지정 위치예시
--domain 플래그infisical login --domain="https://secrets.example.com"
INFISICAL_DOMAIN 환경 변수export INFISICAL_DOMAIN=https://secrets.example.com
.infisical.jsondomain 필드프로젝트별로 인스턴스를 고정
기본값https://app.infisical.com

(구형 변수인 INFISICAL_API_URL도 여전히 인식되지만, 둘 다 설정되어 있으면 INFISICAL_DOMAIN이 우선합니다.)

/api 접미사 없이 기본 URL을 쓰세요. 가장 간단한 방법은 셸 프로필에 한 번 export 해두는 것입니다.

export INFISICAL_DOMAIN="https://secrets.example.com"
infisical login

사람이 노트북에서 쓸 때 infisical login은 브라우저를 열고, 자격 증명을 OS 키체인에 저장합니다. 머신은 다른 방식을 쓰며, 이는 6장에서 다룹니다.


5. 로컬 .env로 프로젝트 부트스트랩하기

값이 가득한 .env를 가진 기존 프로젝트가 있다고 합시다. 목표는 하나씩 타이핑하지 않고 이 값들을 Infisical에 올리는 것입니다.

5.1 디렉터리를 프로젝트에 연결하기

프로젝트 루트에서:

cd ~/code/orders-api
infisical init

이 명령이 .infisical.json을 생성합니다.

{
  "workspaceId": "63ee5410a45f7a1ed39ba118",
  "defaultEnvironment": "dev",
  "domain": "https://secrets.example.com"
}

이 파일은 커밋하세요. 시크릿은 들어있지 않고, 이 디렉터리가 어느 프로젝트와 어느 인스턴스에 속하는지만 담겨 있습니다. domain 필드를 넣어두면 팀원들이 각자 INFISICAL_DOMAIN을 export 할 필요가 없습니다.

온 김에, 진짜 시크릿이 그 파일을 따라 git에 들어가지 않도록 막아둡니다.

grep -qxF '.env' .gitignore || echo '.env' >> .gitignore

5.2 기존 .env 밀어 올리기

infisical secrets setKEY=value 쌍을 개수 제한 없이 받으므로, 파일 전체를 한 번의 호출로 올릴 수 있습니다.

#!/usr/bin/env bash
# import-env.sh — 로컬 dotenv 파일을 Infisical 환경으로 밀어 올린다
set -euo pipefail

ENV_FILE="${1:-.env}"
ENV_SLUG="${2:-dev}"
SECRET_PATH="${3:-/}"

[[ -f "$ENV_FILE" ]] || { echo "파일이 없습니다: $ENV_FILE" >&2; exit 1; }

# KEY=value 줄만 남기고 주석, 빈 줄, `export ` 접두사를 제거한다.
mapfile -t PAIRS < <(
  sed -e 's/^[[:space:]]*export[[:space:]]\+//' "$ENV_FILE" \
    | grep -E '^[A-Za-z_][A-Za-z0-9_]*=' \
    | sed -e 's/=[[:space:]]*"\(.*\)"[[:space:]]*$/=\1/' \
          -e "s/=[[:space:]]*'\(.*\)'[[:space:]]*\$/=\1/"
)

(( ${#PAIRS[@]} )) || { echo "$ENV_FILE 에서 KEY=value 줄을 찾지 못했습니다" >&2; exit 1; }

echo "시크릿 ${#PAIRS[@]}개를 env=$ENV_SLUG path=$SECRET_PATH 로 가져옵니다"
printf '  %s\n' "${PAIRS[@]%%=*}"

infisical secrets set "${PAIRS[@]}" --env="$ENV_SLUG" --path="$SECRET_PATH"
chmod +x import-env.sh
./import-env.sh .env dev /
./import-env.sh .env.production prod /

이 스크립트는 export 접두사와 값을 감싼 따옴표를 제거합니다. 단순한 한 줄짜리 명령이 대개 여기서 어긋납니다. 다만 여러 줄에 걸친 값은 의도적으로 처리하지 않습니다. 바로 아래를 보세요.

확인 지점:

infisical secrets --env=dev

5.3 가져오기 전에 알아둘 것들

여러 줄 값과 파일 내용(PEM 키, 서비스 계정 JSON)은 붙여넣지 말고 파일에서 읽게 하세요.

infisical secrets set PRIVATE_KEY=@./private.pem
infisical secrets set GCP_SA_JSON=@./service-account.json

값이 @로 시작한다면 이스케이프해야 합니다: infisical secrets set EMAIL="\@example.com".

키가 스무 개를 넘어가면 폴더로 정리하는 편이 낫습니다.

infisical secrets folders create --name=database --env=dev
infisical secrets set DB_PASSWORD=... --env=dev --path="/database"

--tag를 활용해 나중에 따로 조회하고 싶은 묶음을 표시해 두세요.

infisical secrets set STRIPE_KEY=... --tag=payments --env=prod

5.4 .env 없이 로컬에서 실행하기

개발자 입장에서 여기가 보상입니다.

infisical run --env=dev -- npm run dev

infisical run은 시크릿을 가져와 자식 프로세스에 환경 변수로 주입하며, 디스크에는 쓰지 않습니다. 유용한 플래그:

플래그효과
--env환경 슬러그. 기본값은 dev
--path가져올 폴더. 반복 지정 가능 (--path=/common --path=/api)
--recursive--path 아래 하위 폴더까지 포함
--watch상위에서 시크릿이 바뀌면 명령을 재시작
--commandargv 대신 셸 문자열 실행 (--command="npm run build && npm start")
--projectId사용자가 아니라 머신 아이덴티티로 인증할 때 필수

여기까지 동작하면 로컬 .env를 삭제하세요. 그게 이 모든 작업의 목적입니다.


6. Compose 서버에서 시크릿 사용하기

이제 서버 쪽입니다. 손으로 복사해 둔 .env 파일을 바라보며 docker compose up -d를 돌리는 호스트가 있고, 그 파일을 없애는 것이 목표입니다.

6.1 먼저, 머신 아이덴티티

서버는 브라우저 로그인을 할 수 없습니다. 서버는 머신 아이덴티티로 Universal Auth — 클라이언트 ID와 클라이언트 시크릿 쌍 — 를 사용해 인증합니다.

UI에서 Organization → Access Control → Machine Identities → Create로 이동합니다. 이름(orders-api-prod)을 주고 Add Client Secret을 눌러 두 값을 복사하세요. 시크릿은 이때 한 번만 표시됩니다.

그다음 프로젝트 접근 권한을 부여합니다. Project → Access Control → Machine Identities → Add에서 읽기 전용 역할을 줍니다. 배포 호스트가 시크릿을 쓸 일은 없습니다.

서비스마다, 환경마다 아이덴티티를 하나씩 두세요. 자격 증명을 공유하면 스테이징 침해가 프로덕션 침해가 됩니다.

서버에서:

export INFISICAL_DOMAIN="https://secrets.example.com"

export INFISICAL_TOKEN=$(infisical login \
  --method=universal-auth \
  --client-id="<client-id>" \
  --client-secret="<client-secret>" \
  --plain --silent)

--plain --silent는 JWT만 출력하므로 위처럼 $(...) 안에서 쓸 수 있습니다. 이 토큰은 수명이 짧고(기본 30일), 필요할 때마다 클라이언트 ID/시크릿으로 다시 발급받습니다.

이제 세 가지 패턴 중 하나를 고릅니다.

6.2 패턴 A — 배포 시점에 .env 생성하기

기존 구성에 대한 변경이 가장 적은 방법입니다. .env 파일은 유지하되, 손으로 관리하는 것만 그만둡니다.

#!/usr/bin/env bash
# deploy.sh
set -euo pipefail
umask 077   # 생성되는 .env가 644가 아니라 600이 되도록

export INFISICAL_DOMAIN="https://secrets.example.com"
PROJECT_ID="<your-project-id>"

INFISICAL_TOKEN=$(infisical login \
  --method=universal-auth \
  --client-id="$UA_CLIENT_ID" \
  --client-secret="$UA_CLIENT_SECRET" \
  --plain --silent)
export INFISICAL_TOKEN

infisical export \
  --projectId="$PROJECT_ID" \
  --env=prod \
  --path=/ \
  --format=dotenv > .env

docker compose up -d

--formatdotenv, dotenv-export, json, yaml, csv를 지원합니다.

얻는 것: 단일 진실 공급원, 그리고 키 교체가 scp 대신 재배포로 바뀝니다.

얻지 못하는 것: 평문 파일은 여전히 디스크에 있고, Compose는 .env를 Compose 파일 안의 ${VAR} 치환에도 사용합니다. 따라서 POSTGRES_PASSWORD 같은 이름의 시크릿은 의도와 무관하게 Compose 파일에도 조용히 치환되어 들어갑니다. 솔직히 말하면, 손으로 복사하던 파일에 비해 분명한 개선이지만 세 패턴 중에서는 가장 약합니다.

이 방식도 호스트에 클라이언트 ID와 시크릿은 있어야 합니다. root 소유의 /etc/infisical.env에 모드 600으로 두거나, systemd 유닛의 EnvironmentFile에 넣으세요. 부트스트랩 자격 증명은 언제나 하나는 존재합니다. 목표는 그것이 정확히 하나이고, UI에서 폐기할 수 있게 만드는 것입니다.

6.3 패턴 B — 파일을 아예 만들지 않기

infisical run으로 Compose CLI 자체를 감쌀 수 있습니다.

infisical run \
  --projectId="<your-project-id>" \
  --env=prod \
  -- docker compose up -d

시크릿은 docker compose 프로세스의 환경에 들어갑니다. Compose는 이를 두 가지 용도로 씁니다.

1. Compose 파일 안의 ${VAR} 치환:

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

2. 값 없이 키만 적은 패스스루 항목 — 셸에서 값을 가져옵니다:

services:
  api:
    image: ghcr.io/example/orders-api:1.4.2
    environment:
      - DATABASE_URL
      - STRIPE_SECRET_KEY
      - JWT_SIGNING_KEY

두 번째 형태를 쓰세요. 어떤 시크릿이 어느 컨테이너에 도달하는지 정확히 명시되고, 그 자체가 문서처럼 읽힙니다.

주의할 점 두 가지:

  • 치환에서는 셸 환경이 .env 파일보다 우선합니다. 그래서 이행 기간에 남아 있는 .envinfisical run이 주입한 값을 덮어쓰지는 않지만, 동시에 그 파일이 쓰이지 않는다는 사실도 눈에 띄지 않습니다. 전환이 끝나면 삭제하세요.
  • docker compose up -d는 즉시 반환되고, 컨테이너는 시작 시점의 환경을 그대로 유지합니다. 시크릿이 바뀌면 명령을 다시 실행해야 합니다. 교체된 값이 실행 중인 컨테이너에 저절로 반영되지는 않습니다.

6.4 패턴 C — 컨테이너 안에서 CLI 실행

격리 수준이 가장 높습니다. 각 컨테이너가 시작할 때 자기 시크릿만 가져오므로, 호스트에는 전체 시크릿이 한 번도 존재하지 않습니다.

FROM node:22-alpine

RUN apk add --no-cache curl \
 && curl -1sLf 'https://artifacts-cli.infisical.com/setup.apk.sh' | sh \
 && apk add --no-cache infisical

WORKDIR /app
COPY . .
RUN npm ci --omit=dev

ENTRYPOINT ["infisical", "run", "--projectId=<your-project-id>", "--env=prod", "--"]
CMD ["node", "server.js"]
services:
  orders-api:
    build: .
    environment:
      INFISICAL_TOKEN: ${INFISICAL_TOKEN}
      INFISICAL_DOMAIN: ${INFISICAL_DOMAIN}
export INFISICAL_DOMAIN="https://secrets.example.com"
export INFISICAL_TOKEN=$(infisical login --method=universal-auth \
  --client-id="$UA_CLIENT_ID" --client-secret="$UA_CLIENT_SECRET" --plain --silent)

docker compose up -d --build

여기서 INFISICAL_DOMAINlocalhost URL을 넣으면 안 됩니다. 컨테이너 안에서 그 주소는 컨테이너 자기 자신을 가리킵니다. Infisical이 같은 호스트에서 돌고 있다면 host.docker.internal을 쓰거나, 두 스택을 같은 Docker 네트워크에 두고 서비스 이름으로 접근하세요.

권한이 다른 서비스가 여럿이라면 각각에 별도의 아이덴티티와 별도의 변수(INFISICAL_TOKEN_WEB, INFISICAL_TOKEN_API)를 주고 서비스별로 매핑하세요.

6.5 어떤 패턴을 고를 것인가

A: .env로 내보내기B: infisical run --C: 컨테이너 내 CLI
호스트 디스크의 평문있음없음없음
앱 이미지 변경없음없음Dockerfile + CLI
서비스별 권한 분리불가키 단위로 수동아이덴티티 단위
${VAR} 치환과 함께 동작아니오
적합한 상황기존 호스트 이행대부분의 Compose 구성멀티테넌트 호스트

B에서 시작하세요. 서비스마다 다른 권한이 필요해지면 C로 갑니다. A는 스택 안의 무언가가 진짜로 파일을 요구할 때만 쓰세요.


7. Kubernetes에서 시크릿 사용하기

Kubernetes에서는 프로세스에 시크릿을 주입하지 않습니다. 대신 오퍼레이터가 Infisical 시크릿을 네이티브 Kubernetes Secret 오브젝트로 동기화하게 하고, 그 Secret은 지금까지 쓰던 방식 그대로 소비합니다.

7.1 오퍼레이터 설치

helm repo add infisical-helm-charts \
  'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/'
helm repo update

helm install infisical-secrets-operator \
  infisical-helm-charts/secrets-operator \
  --namespace infisical-operator-system \
  --create-namespace
kubectl get pods -n infisical-operator-system
kubectl get crds | grep infisical

현재 API는 secrets.infisical.com/v1beta1이고, 책임이 깔끔하게 분리된 세 개의 CRD로 구성됩니다.

CRD답하는 질문
InfisicalConnection어떤 Infisical 인스턴스인가, TLS는 어떻게 신뢰하나
InfisicalAuth어떤 머신 아이덴티티로, 어떤 인증 방식으로
InfisicalStaticSecret어떤 시크릿을 어디로, 얼마나 자주 동기화하나

연결·인증·동기화를 한 리소스에 모두 인라인으로 넣던 구형 v1alpha1InfisicalSecret은 폐기 예정입니다. InfisicalPushSecretInfisicalDynamicSecret은 아직 v1alpha1에 있습니다.

7.2 자격 증명 Secret

여기에는 닭과 달걀 문제가 있습니다. 오퍼레이터가 자격 증명을 가져오려면 자격 증명이 필요합니다. 6.1처럼 머신 아이덴티티를 만든 뒤:

kubectl create secret generic universal-auth-credentials \
  --namespace=orders \
  --from-literal=clientId="<client-id>" \
  --from-literal=clientSecret="<client-secret>"

7.3 세 개의 리소스

# 1. Infisical 인스턴스의 위치
apiVersion: secrets.infisical.com/v1beta1
kind: InfisicalConnection
metadata:
  name: self-hosted-infisical
  namespace: orders
spec:
  address: https://secrets.example.com
---
# 2. 인스턴스에 인증하는 방법
apiVersion: secrets.infisical.com/v1beta1
kind: InfisicalAuth
metadata:
  name: orders-auth
  namespace: orders
spec:
  infisicalConnectionRef:
    name: self-hosted-infisical
    namespace: orders
  method: universal
  universal:
    clientIdRef:
      name: universal-auth-credentials
      namespace: orders
      key: clientId
    clientSecretRef:
      name: universal-auth-credentials
      namespace: orders
      key: clientSecret
---
# 3. 무엇을 동기화해서 어디에 넣을 것인가
apiVersion: secrets.infisical.com/v1beta1
kind: InfisicalStaticSecret
metadata:
  name: orders-api-secrets
  namespace: orders
spec:
  infisicalAuthRef:
    name: orders-auth
    namespace: orders
  syncOptions:
    refreshInterval: 60s
  sources:
    - projectId: <your-project-id>
      environmentSlug: prod
      secretPath: /
  targets:
    - name: orders-api-secrets
      namespace: orders
      kind: Secret
      creationPolicy: Owner
kubectl apply -f infisical.yaml

확인 지점:

kubectl get infisicalconnection,infisicalauth,infisicalstaticsecret -n orders
kubectl get secret orders-api-secrets -n orders -o jsonpath='{.data}' | jq 'keys'

출력 컬럼에서 connection과 auth는 Ready, static secret은 Synced로 표시됩니다. 문제가 있을 때는 kubectl describe infisicalstaticsecret orders-api-secrets -n orders가 condition에 원인을 담아줍니다.

address/api 접미사 없이 https://secrets.example.com으로 쓰세요. 오퍼레이터가 알아서 붙입니다. 사설 CA 뒤에 있는 인스턴스라면 검증을 끄지 말고 connection의 spec.tls에 TLS 설정을 추가하세요.

7.4 스펙에 대한 실무 메모

sources는 리스트입니다. 하나의 target에 여러 출처를 합칠 수 있습니다. 공용 설정과 서비스 전용 키를 함께 가져오는 식입니다.

sources:
  - projectId: <project-id>
    environmentSlug: prod
    secretPath: /common
  - projectId: <project-id>
    environmentSlug: prod
    secretPath: /orders
    recursive: true
    tagSlugs: ["payments"]

각 source는 projectId 또는 projectSlug 중 하나에 environmentSlugsecretPath를 함께 받습니다. recursive는 하위 폴더를 포함하고, tagSlugs는 태그로 필터링합니다.

targets도 리스트이고, Secret 대신 ConfigMap을 쓸 수 있습니다(kind: ConfigMap — 민감하지 않은 설정에만). creationPolicy가 중요합니다.

creationPolicy동작
Owner오퍼레이터가 Secret을 소유. CR을 지우면 함께 GC됩니다. 기본으로 택할 값.
OrphanSecret이 CR보다 오래 남습니다. 이행 작업이나 다른 컨트롤러가 건드리는 Secret용.

syncOptions.refreshInterval 은 폴링 주기입니다(60s가 무난합니다). instantUpdates: true를 켜면 푸시 기반 업데이트를 받아서, 키 교체가 주기를 기다리지 않고 전파됩니다.

targets[].template 은 앱이 시크릿 하나당 키 하나가 아니라 특정 형식을 원할 때 출력을 재구성합니다. 렌더링된 설정 파일이나, 조각을 조합한 DATABASE_URL 같은 것들입니다. engineVersion: v1을 설정하고 data 아래에 Go 템플릿을 넣습니다.

7.5 소비하기, 그리고 변경 시 재시작

apiVersion: apps/v1
kind: Deployment
metadata:
  name: orders-api
  namespace: orders
  annotations:
    secrets.infisical.com/auto-reload: "true"
spec:
  replicas: 3
  selector:
    matchLabels:
      app: orders-api
  template:
    metadata:
      labels:
        app: orders-api
    spec:
      containers:
        - name: api
          image: ghcr.io/example/orders-api:1.4.2
          envFrom:
            - secretRef:
                name: orders-api-secrets

secrets.infisical.com/auto-reload: "true" 어노테이션이 사람들이 놓치는 부분입니다. 환경 변수는 프로세스 시작 시 한 번만 읽히므로, 이 어노테이션이 없으면 교체된 시크릿이 Kubernetes Secret 안에만 들어앉아 있고 실행 중인 파드들은 누군가 재배포할 때까지 계속 예전 값을 씁니다. 어노테이션이 있으면 동기화된 Secret이 바뀔 때 오퍼레이터가 Deployment를 롤링합니다.

7.6 정적 자격 증명 없이 클러스터 내부에서 인증하기

Universal Auth는 결국 클라이언트 ID와 시크릿이 Kubernetes Secret에 들어있다는 뜻입니다. .env보다는 낫지만, 여전히 직접 교체해야 하는 장수명 자격 증명입니다.

클러스터가 더 나은 방법을 지원한다면 InfisicalAuth는 다른 인증 방식들도 제공합니다. 각각은 플랫폼이 이미 보증하는 워크로드 아이덴티티에 기반합니다.

method사용하는 것
kubernetes이 클러스터의 ServiceAccount 토큰
awsIam노드/파드의 IAM 역할
gcpIdTokenWorkload Identity
azureAzure 관리형 아이덴티티
ldapLDAP 바인드

클러스터 옆에서 셀프 호스팅 인스턴스를 돌린다면 method: kubernetes가 가장 자연스럽습니다. 교체할 정적 시크릿이 없고, 아이덴티티가 ServiceAccount 단위로 한정됩니다.

spec:
  method: kubernetes
  kubernetes:
    identityIdRef:
      name: infisical-identity-id
      namespace: orders
      key: identityId
    serviceAccountRef:
      name: orders-api
      namespace: orders

프로덕션이라면 추가 설정 비용을 들일 가치가 있습니다. 먼저 universal auth로 파이프라인이 동작하는지 확인한 뒤 전환하세요.

7.7 오퍼레이터가 해결해주지 않는 것 하나

동기화된 오브젝트는 평범한 Kubernetes Secret입니다. 암호화가 아니라 base64입니다. 해당 네임스페이스에 get secrets 권한이 있는 사람은 누구나 읽을 수 있습니다. Infisical은 배포 문제를 해결하는 것이지 Kubernetes의 저장 모델을 바꾸지는 않습니다. 그래도 할 일은 남아 있습니다. etcd 저장 시 암호화를 켜고, 네임스페이스 전체 시크릿 읽기 권한을 함부로 주지 않는 RBAC를 구성하세요.


정리하면

상황방식디스크의 시크릿
로컬 개발infisical run -- npm run dev없음
Compose 서버infisical run -- docker compose up -d없음
Kubernetes오퍼레이터 → 네이티브 Secret → envFrom클러스터에만
CI머신 아이덴티티 + INFISICAL_TOKEN없음

관통하는 원리는 모든 클라이언트가 자기 자신으로 인증하고 허용된 것만 가져간다는 점입니다. 키 교체는 UI 한 곳에서의 한 번의 수정이 되고, 유출된 자격 증명의 폭발 반경은 폐기 가능한 아이덴티티 하나로 줄어듭니다.

솔직한 단서도 달아둡니다. 이제 다른 모든 것이 의존하는 상태 저장 서비스를 운영하게 되므로, 그 Postgres에는 백업 전략이 필요하고 그 가용성이 곧 내 서비스의 가용성이 됩니다. ENCRYPTION_KEY는 전체 데이터 손실의 단일 지점이므로, 그 문자열 하나의 백업을 데이터베이스 자체만큼 진지하게 다뤄야 합니다. 그리고 Kubernetes에서 시크릿은 여전히 base64로 etcd에 저장됩니다. Infisical은 바닥을 상당히 끌어올려 주지만, 생각할 필요 자체를 없애주지는 않습니다.

참고 자료

Related Posts