2026년 11월 10일 화요일

[English] Building an Android App by Vibe Coding with Claude Code: A Real Example (Quit Smoking)

This is how I built an Android app through conversation alone in a fresh Claude Code session, all the way to the edge of a Play Store release.

Instead of writing code myself, I focused on deciding what to build, checking the results, and steering the direction.

  • Example app: 금연관리 / Quit Smoking (Kotlin, Jetpack Compose, local storage only, AdMob ads)
  • Timeline: October 9, 2026 to November 2026 (one conversation, resumed many times)
  • What I did: ideas, mockups and artwork, decisions, checking screens, anything involving accounts, passwords and keys
  • What Claude did: design, code, tests, builds, emulator checks, source research, docs, Git

0. What you need

ItemIn this exampleNotes
Claude CodeDesktop app (Code tab)Turn on Remote Control to follow along from your phone
JDK 17D:\dev\java\temurin_jdk17Run Gradle with JDK 17 (the JBR bundled with Android Studio may be too new)
Android Studio + SDKSDK in %LOCALAPPDATA%\Android\SdkCreate one emulator (AVD)
Git + GitHub accountSource in a private repo, privacy policy on public GitHub Pages
Image generatorChatGPTAvatars, mascots, icon, a short video
Tip: When your PC sleeps, the remote connection and the emulator drop. Set "never sleep when plugged in" to save yourself trouble.

1. Turn the idea into two documents

Before the first prompt, write down what you want to build. These two files become the reference for every session after.

spec.md: features and formulas

  • Screens, features, and formulas (e.g. money saved = cigarettes not smoked × price per cigarette)
  • A list of open questions (- [ ] How far should the avatar drop after a relapse?), checked off as you decide

CLAUDE.md: rules Claude reads every time

Claude Code reads this file automatically at the start of each session, so whatever you put here gets followed every time.

  • Stack/structure: Kotlin, Compose, MVVM, DataStore, Room, WorkManager, and so on
  • Core rules (the ones that mattered most for this app)
    • All calculations live in a pure function QuitCalculator, always unit-tested
    • Health figures are "estimates for reference", never medical claims
    • Never invent recovery percentages or timelines without a source
    • No blaming language after a relapse
    • Only 3 required inputs
  • Build order: 1) project, theme, tabs 2) calculator and tests 3) welcome, home, settings 4) missions, SOS, relapse, badges 5) notifications, avatar, ads
  • Design tokens: colors, font, touch target sizes
CLAUDE.md is not write-once. Whenever a decision changes, ask Claude to "update CLAUDE.md too" so the next session knows.

Mockups

  • I made HTML mockups of the screens and put them in mockup/, with one file per screen in mockup/screens/.
  • In the first conversation I said "the attachment is the mockup for this app", and Claude followed its design tokens and layout.

2. First session: build in order

A first prompt can be as simple as:

Read CLAUDE.md and spec.md and start with step 1 of the build order.
The mockups are in mockup/. JDK and Android Studio are installed under D:\dev.

At each step Claude:

  1. Writes the code
  2. Builds, runs unit tests and lint
   ./gradlew assembleDebug testDebugUnitTest lintDebug
  1. Installs on the emulator and checks the screen with a screenshot
  2. Summarizes and proposes the next step

You only need to look at the screenshot or the APK and say "good" or "this looks off".

Where this project first got stuck: backslashes in the SDK path, Gradle not running on the newest JBR, and the emulator's AVD path. Claude read the error messages and fixed them.

3. Refining through conversation

Most features came out of this loop:

You: an idea or a pain point   →  Claude: options + a recommendation + reasons
You: a decision ("yes", "B")   →  Claude: implement → test → check the screen → APK
You: try it on your phone, give feedback  →  (repeat)

Real examples from this app

What I saidWhat came out
"How about watching ads during the 3-minute challenge?"An "ad" mode in the 3-minute challenge with a reward: collect 38 cheer angels, get a badge when complete, then start over
"Isn't anything other than breathing or ads pointless?"Modes cut down to breathing and ads; the rest became a "try these too" checklist
"What happens to the health missions if I smoke one cigarette?"After a relapse only short-term missions restart; long-term ones are kept (no evidence one cigarette resets long-term risk)
"Can you show recovery % for skin and lungs?"Researched the sources and concluded no. No percentages, only sourced explanations
"Can '12 hours to a few days' be an exact number of days?"Instead of a made-up number: the time the mission completes plus the range other sources give
"The icon is too small to tell it's an angel"Compared variants, then made a dedicated icon in ChatGPT and generated adaptive and themed icons automatically
"What about tracking time saved?" → "Drop the book comparison, it differs per person"Added time saved, then removed the comparison again
Key point: don't hand decisions to Claude. Ask for options and choose yourself. If you don't like the result, roll it back.

4. Art, video and other assets

Assets that code can't make came from an image generator, and cropping and conversion were kept as scripts so they can be rerun.

AssetSourceProcessing script
Avatars (4 looks × 2 genders × 5 stages)ChatGPT sheet imagesmockup/avatars/crop_sheets.py
Craving devil and cheer angel badges, 38 collectible itemsChatGPT character sheetsmockup/mascot/crop_mascot.py, crop_items.py
App iconChatGPT "broken cigarette + sprout"mockup/icon/make_icon.py (adaptive, monochrome, Play 512px)
30-second story video (Korean/English)ChatGPT videoffmpeg re-encode (mockup/story/README.md)
Tip: Attach the image to the chat and say "crop this sheet into the 5 avatar stages". For large art, always preview how it looks at small size (48px).

5. Check facts against sources first

A health app is full of lines like "20 minutes after quitting, your heart rate drops". For these:

  1. Have Claude read the official sources directly (CDC, WHO, ACS, NHS).
  2. Summarize them in a table in docs/health-sources.md (use / on hold / do not use).
  3. Add a rule to CLAUDE.md: only items marked "use" in that table may appear in the app.

Figures without a source, such as "lung function improves 10% in 3 to 9 months" or hair regrowth rates, never made it into the app.


6. Habits that keep quality up

  • Pure functions + unit tests for all calculations. About 50 tests ran on every change.
  • Check on the emulator yourself. It catches the "the code changed but my phone shows the old screen" mistake. Make sure the new APK was actually installed.
  • Test on a real device. Install the debug APK Claude sends, use it, and give feedback with screenshots.
  • Lint. Before release, run lintRelease and confirm zero errors.
  • Claude is sometimes wrong (for example, it said the home screen had a banner ad when only the missions screen did). Confirm results on screen.

7. Getting ready to release

StepWhatWho
App name and IDQuit Smoking, com.example.quitsmoking style ID (can't change after the first upload)You decide
LanguagesEnglish (default) + Korean, with an in-app language pickerClaude
Upload keyCreated with keytool; you type the password yourself; keystore.properties stays out of GitYou
targetSdkRaised to match Play policy (API 36+ required from August 31, 2026)Claude, after checking the policy
AdMobYou create the app and ad units → Claude wires the IDs (real IDs in release only, test IDs in debug)Together
Privacy policyClaude drafts it → published on GitHub PagesTogether
app-ads.txtPublished at the root of the same Pages siteClaude
EU consent (UMP) messageSet up in the AdMob console → publishTogether
AAB build./gradlew bundleRelease, then verify it's signed with the upload keyClaude
Play ConsoleRegister the app, data safety, content rating, closed testing (new personal accounts need 12 testers for 14 days)You
Claude doesn't type passwords, payment details or sign-ins for you. Do those yourself, say "done", and it continues from there.

8. Git / GitHub

git init -b main
git add -A
git commit -m "Initial commit"
git remote add origin https://github.com/<user>/<repo>.git
git push -u origin main
  • Put keys, passwords and build output (*.jks, keystore.properties, local.properties, dist/, build/) in .gitignore first, and check with git ls-files before committing.
  • Claude commits and pushes after each feature.
  • If you're editing in VS Code at the same time, tell Claude which files you're touching so it stays away from them.

9. Picking up in a new session

When a session gets long, the early part is summarized, and a new session remembers nothing. So:

  • Record decisions in CLAUDE.md / spec.md / docs/. A new session reads them automatically.
  • A first message might be:
  Read CLAUDE.md, spec.md and docs/ and summarize where things stand.
  Then let's continue preparing the Play Console closed test.
  • git log --oneline also shows the progress.

10. Requests I used often

SituationRequest
New feature idea"What if we added X? Give me options and a recommendation"
Decision"Go with B" / "Default 7 minutes, add it everywhere"
Check"Check it on the emulator with a screenshot"
Delivery"Let me download the latest APK" / "Build the AAB"
Fact check"Find the sources and check. Don't use numbers without a source"
Docs"Update CLAUDE.md and spec.md too"
Roll back"Drop the book comparison and just show the time"
Backup"Commit and push to GitHub"

11. Looking back

  • Writing good rules in CLAUDE.md up front paid off the most. Principles like "no numbers without a source" and "no blaming language" kept the app's tone consistent as features piled up.
  • Build in small steps, try each one on your phone, and say what you think right away. That's the fastest loop.
  • For hard-to-undo things like accounts, keys, payments and publish buttons, either do them yourself or have Claude ask before it acts.

Paperclip(AI Agent 오케스트레이션 앱) 사용하기까지: 사전 설치 항목과 시작 방법

Windows 11 PC에서 Paperclip을 처음 설치해 서버를 띄우고 Agent 연결 테스트까지 해 본 과정을 정리했습니다. 글에서 [직접 확인]은 제가 2026-11-10에 직접 해 본 것, [문서상]은 공식 문서에 적힌 설명이지만 제가 실행해 보지 않은 것입니다.

1. Paperclip이 뭔가

오픈소스(MIT) AI Agent 오케스트레이션 도구입니다. Node.js 서버와 React UI로 구성되어 있고, Agent에게 티켓(작업)을 배정한 뒤 승인·예산·이력을 한곳에서 관리합니다.

2. 사전 설치 항목

Node.js 24.11 이상 (필수) [직접 확인]

Node 22.21.0 환경에서 paperclipai 점검(doctor)이 Node.js 22.21.0 is unsupported로 실패했고 서버가 시작되지 않았습니다. Windows에서는 winget으로 LTS를 설치했습니다.

winget install --id OpenJS.NodeJS.LTS --exact
  • 설치 중 관리자 권한 창이 눈에 보이지 않았지만 "설치 성공"으로 끝났습니다.
  • 새 터미널을 열어 node -v 로 확인합니다(저는 26.13.0). 기존 터미널은 옛 버전 경로를 그대로 쓸 수 있습니다.

Claude 구독 로그인 방식을 쓸 경우 [문서상 + 환경 확인]

README상 Python 3와 Claude CLI(Claude Code)가 PC에 있어야 합니다. 제 환경은 Python 3.13, Claude Code 2.1.x 였습니다. API 키 방식을 쓸 때의 요건은 확인하지 못했습니다.

3. 버전 함정: @latest 를 꼭 붙이세요 [직접 확인]

npx paperclipai ... 처럼 @latest 없이 실행하면 npx 캐시에 남은 옛 버전(2026.824.1)이 실행되어 test-drive 명령이 없다는 오류가 났습니다. 같은 시점에 npx --yes paperclipai@latest --version 은 2026.1106.0 이었습니다.

또 npx가 Ok to proceed? (y) 를 물을 때 한글 입력 상태에서 ㅛ 를 치면 취소됩니다. 영문 상태에서 y 를 입력하거나 --yes 옵션을 쓰세요.

4. 서버 시작 [직접 확인]

npx --yes paperclipai@latest run --data-dir D:\paperclip-data
  • 먼저 점검(doctor)을 돌리고, 내장 PostgreSQL(포트 54329)을 띄운 뒤, http://127.0.0.1:3100 에서 UI가 열립니다.
  • 로컬 전용이며 로그인이 없는 모드(local_trusted)였습니다.
  • 같은 --data-dir 로 다시 켜면 만들어 둔 조직과 Agent가 이어집니다.
  • 끌 때는 실행 중인 터미널에서 Ctrl-C.

test-drive 는 키가 없으면 바로 종료됩니다

test-drive 는 모델 키가 없으면 No credential found. Set ANTHROPIC_API_KEY... 라며 서버를 띄운 직후 종료했습니다. 키 없이 서버만 켜려면 run 을 쓰세요.

텔레메트리 끄기

익명 사용 통계 전송이 기본으로 켜져 있습니다. 끄려면 환경변수를 설정합니다.

$env:PAPERCLIP_TELEMETRY_DISABLED = '1'

(다른 셸에서는 PAPERCLIP_TELEMETRY_DISABLED=1)

5. 첫 화면 온보딩 [직접 확인]

조직 이름 입력 → Agent(예: CEO/일반) 생성 → "Connect a model" 순서입니다.

여기서 "Claude Subscription" 로그인을 시도했더니 Sign-in ended before the account was connected 로 계속 실패했습니다. 그런데 이 연결(managed connection)은 필수가 아니었습니다.

  • Agent 설정(Harness/Runtime)에 Existing authentication — not managed by Connections 가 표시됩니다.
  • 이 경우 PC에 이미 로그인된 Claude Code(Local credentials)를 그대로 씁니다.
  • 같은 화면 아래 "Test your agent → Run test" 를 눌러 Connection successful / runtime checks passed 를 확인했습니다. 처음 한 번은 Couldn't connect 가 떴고 다시 누르니 성공했습니다.

6. 시작 직후 알아둘 것

  • 대시보드에 All agents are paused 경고가 뜹니다. 새 Agent는 중지 상태로 시작하므로 작업을 돌리려면 Agent를 resume 해야 합니다. [직접 확인: 경고 표시]
  • 예산은 기본 무제한이었습니다. Audit → Budgets 에서 월 상한을 정해 두는 것을 권합니다. [문서상] 80%에서 경고, 100%에서 해당 범위가 자동 중지되며, 이미 실행 중인 작업은 조금 더 쓸 수 있습니다.

7. 기본 사용 흐름 [문서상 — 직접 실행해 보지 않음]

  1. New Task 로 작업 생성
  2. 담당 Agent 지정(배정하면 Agent가 깨어남)
  3. 작업 상세·Audit, Runs 에서 진행 확인
  4. 코멘트로 추가 지시
  5. 승인 요청은 Inbox/승인 화면에서 승인·반려·수정요청

Agent는 기본적으로 타이머가 아니라 배정·코멘트·루틴 같은 이벤트로 깨어납니다. 솔직히, 제가 직접 해 본 것은 Agent 연결 테스트까지이고 실제 작업 배정 후 실행은 아직 해 보지 않았습니다.

8. Agent·스킬 설정을 파일로 관리 [직접 확인]

내보내기:

npx paperclipai@latest company export <조직ID> --out <폴더> --include company,agents,skills --api-base http://127.0.0.1:3100

COMPANY.md, agents/<이름>/AGENTS.md, skills/.../SKILL.md, .paperclip.yaml 로 이루어진 마크다운 패키지가 생깁니다. 조직ID는 company list 로 확인합니다.

가져오기(미리보기 후 적용):

npx paperclipai@latest company import <폴더> --target existing -C <조직ID> --include agents --dry-run
npx paperclipai@latest company import <폴더> --target existing -C <조직ID> --include agents --yes

이 방법으로 Agent 4개를 등록하는 데 성공했습니다.

9. 보안 주의 [직접 확인]

내보낸 Agent 설정 기본값에 다음이 들어 있었습니다. 실제 사용 전에 검토하세요.

  • dangerouslySkipPermissions: true (권한 확인 없이 도구 사용)
  • Agent 생성·스킬 생성 권한 true
  • heartbeat 300초

그 밖에:

  • Claude 로그인에 만료 경고가 뜰 수 있습니다. claude auth login 으로 갱신합니다.
  • 회사 PC라면 외부 패키지 설치와 모델 호출 경로에 대한 사내 승인을 먼저 받으세요.

정리

항목상태
Node 24.11+ 설치, 서버 시작, UI 접속직접 확인
Agent 생성, 연결 테스트 성공직접 확인
설정 export/import직접 확인
작업 배정·실행·승인 흐름, 예산 동작문서상 설명만, 확인하지 못함

다음 글에서는 실제 작업을 배정해 돌려 본 결과를 적어 보겠습니다.

2026년 11월 7일 토요일

2010~2011년 Android 앱 만들기 기록: 첫 앱부터 간단한 게임까지

2010년 말부터 2011년 초까지 Android 앱을 만들어 당시 Android Market(지금의 Google Play 이전 이름)에 올린 기록을 한곳에 정리했습니다. 각 앱은 원래 짧은 글로 따로 남겨 두었는데, 순서대로 보면 한 가지 흐름이 보여서 묶었습니다. 앱별 화면 캡처는 아래 원문 링크에 있습니다.

한눈에 보기

  • 2010년 11월 11일 - 첫 번째 앱: 친구 위치 찾기 (Find my friends)
  • 2010년 11월 11일 - 두 번째 앱: System Info (폰 정보 보기)
  • 2010년 11월 11일 - 세 번째 앱: 1 to 50 숫자 찾기 게임
  • 2010년 11월 29일 - 숫자 더하기 게임
  • 2010년 11월 29일 - 구구단 게임
  • 2011년 4월 3일 - 사칙 연산자 찾기 게임

1. 첫 번째 앱: 친구 위치 찾기

Android 친구 위치 찾기 앱 화면

회사 스터디 프로젝트로 만든 앱입니다. 전화번호부에서 친구를 추가하면 그 친구의 위치를 Google 지도에 표시해 주고, 상대방도 이 앱을 설치해야 동작합니다. Google Maps 매시업, 서블릿, JDBC, 연락처, 음성 인식을 조합해서 만들었습니다.

만든 뒤 Google Latitude라는 거의 같은 서비스가 이미 있다는 것을 알게 되었고, 서버를 계속 관리하는 부담도 커서 일반 공개는 하지 못했습니다. 동료들만 사용한 앱입니다. 원문: My First Android App.. ( Find my friends.. )

2. 두 번째 앱: System Info

Android System Info 앱 화면 1
Android System Info 앱 화면 2

사용자의 폰 정보를 보여 주는 앱입니다. 올린 지 일주일 만에 다운로드가 1,000건에 도달했고, 위젯 기능을 추가하던 중이었습니다. 원문: 두번째 Android App - System Info 보기..

3. 세 번째 앱: 1 to 50

Android 1 to 50 숫자 찾기 게임 화면

동료의 요청으로, iPhone에 있던 "1 to 50" 숫자 찾기 게임을 보고 Android 버전을 만들었습니다. 같은 앱에 1 to 30 게임도 함께 들어 있습니다. iPhone 버전이 인기 있던 덕분인지 첫 주 다운로드가 약 8,000건이었습니다. 원문: 세번째 Android Program ( 1 to 50 )

4. 숫자 더하기 게임

Android 숫자 더하기 게임 화면

1부터 50까지의 숫자를 이용한 게임으로, 반나절 만에 만들었습니다. iPhone 게임인 "도전 구구단"과 많이 닮았다고 적어 두었습니다. 원문: 숫자 더하기 게임..

5. 구구단 게임

Android 구구단 게임 화면

앞의 숫자 더하기 게임을 조금 고쳐서 약 30분 만에 만든 구구단 게임입니다. 이 정도면 게임이라고 부를 수 있을지 스스로 물어본 글이기도 합니다. 원문: 구구단 게임

6. 사칙 연산자 찾기 게임

Android 사칙 연산자 찾기 게임 화면

간단한 수식에서 빠진 연산자를 찾는 게임입니다. 이미 만들어 둔 게임을 조금 수정해서 마켓에 올렸습니다. 원문: 사칙 연산자 찾기 게임

정리

  • 첫 앱은 서버가 필요한 복잡한 구조였지만 공개하지 못했고, 이후 앱은 서버 없이 폰 안에서 끝나는 단순한 앱이었습니다.
  • 한 번 만든 게임 화면과 로직을 조금씩 바꿔 새 앱으로 내는 방식으로, 30분에서 반나절 만에 앱 하나를 만들었습니다.
  • 다운로드 수는 당시 Android Market 기준이며, 본문에 있던 마켓 링크는 지금은 열리지 않을 수 있습니다.

이 글은 2010~2011년에 쓴 기록 여섯 편을 2026년에 다시 정리한 것입니다.

AI Agent들이 일하는 빌딩, AI Office 만들어 보기 (Three.js)

AI Agent가 여러 개 돌아가면 터미널 로그만으로는 "지금 누가 무엇을 하는지" 알기 어렵습니다. 그래서 Agent들을 사무실에 앉혀 놓고 일하는 모습을 눈으로 볼 수 있게 만들어 보았습니다. 이 글은 그 AI Office의 컨셉과 구조, 그리고 Three.js로 만든 3D 사무실 두 가지를 소개합니다.

1. 컨셉: Agent들의 빌딩

  • 빌딩(Tower): 시작 화면은 층 목록이 있는 빌딩입니다. 층을 고르면 해당 층의 Office로 입장합니다.
  • 사무실(Office): 층마다 역할이 다른 Agent들이 자기 자리에서 일합니다. 한 명은 팀장 역할로 전체를 조율하고, 나머지는 분석, 조사, 작성, 검수처럼 맡은 일이 나뉘어 있습니다.
  • 말풍선과 장면 전환: Agent가 일을 진행하면 말풍선으로 상태를 보여 주고, 단계가 바뀌면 장면이 자동으로 전환됩니다.
  • 사람은 마지막에 승인: 사람이 목표를 정하고 중간에 확인하며, 마지막 인수는 반드시 사람이 직접 승인해야 끝납니다.
  • 캐릭터 컨셉 교체: 설정에서 Agent 모습을 동물, 로봇, 정령, 일러스트로 바꿀 수 있습니다.

2. 두 가지 모드

  • 시연 모드: index.html을 브라우저로 열면 서버 없이 고정 시나리오를 재생합니다.
  • LIVE 모드: node server.js를 실행하면 이 컴퓨터에서 실제 Agent를 실행하고 진행 상황을 화면에 표시합니다.

서버는 Node 내장 모듈만 쓰고(외부 패키지 없음) 127.0.0.1에만 열립니다. 실제 Agent는 로컬에 설치한 Claude Code CLI를 호출하며, Bash와 웹 접근은 막은 채 실행합니다. 입력 자료가 API로 전송되고 사용량 비용이 든다는 점은 미리 알고 써야 합니다.

3. 3D 사무실 ① 노을 도시 위의 유리벽 사무실

  • Three.js로 만든 장면으로, 노을 도시 위에 떠 있는 유리벽 사무실입니다.
  • 하늘에서 환경광 만들기: 하늘 그라데이션(해, 별 포함)을 환경 맵으로 만들어 재질에 반사시킵니다. 같은 방식으로 노을, 낮, 밤을 바꿀 수 있습니다.
  • 그림자: 해 위치에서 만든 부드러운 그림자가 바닥과 가구에 드리웁니다.
  • 가구 모델: Kenney Furniture Kit(CC0, kenney.nl)에서 고른 GLB 모델을 GLTFLoader로 불러옵니다. 모델을 불러오지 못하면 코드로 만든 단순 가구로 대신하도록 해서, 파일이 없어도 화면은 뜨게 했습니다.

4. 3D 사무실 ② 코드만으로 만든 CI/CD 사무실

  • 따뜻한 오크 톤의 사무실에 업무석 6곳과 회의 공간, 라운지, 커피바가 있습니다. 로봇 6대가 업무석에 앉아 있고, 사람 역할의 일러스트가 한쪽에 서 있습니다.
  • 외부 3D 모델 없이 책상, 의자, 소파, 서가, 화분, 펜던트 조명까지 전부 기본 도형(Box, Cylinder, Sphere)을 조합해 만들었습니다. 모니터 화면은 canvas에 그려 텍스처로 입혔습니다.
  • OrbitControls로 드래그 회전과 휠 확대, 버튼으로 전체, 회의 공간, 업무 공간, 라운지 시점 전환, 로봇을 클릭하면 역할 카드가 나옵니다.
  • 업무 시연: 사람이 목표를 주면 로봇들이 말풍선으로 주고받으며 Build, Test, Deploy 흐름을 진행하고, 마지막에 사람이 승인하는 짧은 시나리오가 재생됩니다.
  • three.js r180(MIT)을 vendor 폴더에 넣고 importmap으로 불러오기 때문에 빌드 도구가 필요 없습니다.

참고: 이 CI/CD 사무실은 다른 AI 도구가 만든 Three.js 사무실 예제를 가져와 문구와 역할, 링크를 바꾼 것입니다. 노을 도시 사무실과 LIVE 연동 부분은 직접 구현했습니다.

5. 만들면서 배운 점

  1. 보이게 하면 이해가 빨라진다: 로그 대신 장면과 말풍선으로 보여 주니 어느 단계에서 막혔는지 바로 보입니다.
  2. 마지막 승인은 사람이: 자동화가 편해질수록 "누가 최종 확인하는가"를 화면에 박아 두는 편이 안전합니다.
  3. 빌드 없는 구성의 장점: 외부 패키지 없이 node server.js만으로 돌아가니 다른 PC로 옮기기도 쉽습니다.
  4. 실패에 대비한 대체 경로: 모델 로딩 실패 시 대체 가구처럼, 외부 자원이 없어도 화면이 깨지지 않게 두면 시연이 안정적입니다.

다음에 해볼 것

  • 층별 Office를 더 늘리고 Agent 역할을 층마다 다르게 구성
  • 3D 사무실의 Agent 상태를 LIVE 이벤트와 직접 연결
  • 모바일 화면 대응

이 글의 화면과 문구는 시연용 예시이며, 실제 업무 자료는 포함하지 않았습니다.

[English] Docker Daemon Is Not Running in WSL Ubuntu: How to Start It

Korean original: docker daemon is not running in WSL Ubuntu

Symptom

When you run a docker command in WSL, you see this message. It means the Docker daemon is not running.

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

Quick fix

  1. Launch WSL with administrator privileges.
  2. Switch to root with su - root.
  3. Run dockerd.

You can also try starting it as a service with sudo service docker start.

Check that the daemon is running

If docker info or docker ps prints a result without errors, the daemon is running.

Keep it running after you close the terminal

If you run dockerd in the foreground, the daemon stops when you close that terminal. To run it in the background, use the command below and read /tmp/dockerd.log if something goes wrong. After a full WSL shutdown you need to start it again.

sudo dockerd > /tmp/dockerd.log 2>&1 &

Start Docker automatically

  • On WSL2 with systemd enabled, Docker can be configured to start automatically when WSL boots.
  • On Windows, using Docker Desktop is another option.

Related error

If docker-compose fails with an iptables error such as "Unable to enable NAT rule", see the Korean post docker-compose error (Unable to enable NAT rule). The workaround there is a temporary one, and WSL2 is recommended over WSL1.

Originally written in 2020 in Korean. This English version was added in November 2026.

[English] Push a Docker Image to Google Cloud from WSL (Artifact Registry)

Korean original: Push Docker Image to GCR from WSL

Important: gcr.io is no longer available

Google Container Registry (gcr.io) has been shut down, and image writes have been blocked since March 18, 2025. Use Artifact Registry instead, or migrate your existing gcr.io addresses to it. The commands below use the Seoul region (asia-northeast3). Replace PROJECT_ID and REPO with your own values.

gcloud auth configure-docker asia-northeast3-docker.pkg.dev
gcloud artifacts repositories create REPO --repository-format=docker --location=asia-northeast3
docker tag config-server asia-northeast3-docker.pkg.dev/PROJECT_ID/REPO/config-server:latest
docker push asia-northeast3-docker.pkg.dev/PROJECT_ID/REPO/config-server:latest

What happened in the original 2020 log

The first push to the old gcr.io address failed with an error saying there was no active gcloud account, followed by an unauthorized error. The fix was to log in with gcloud auth login, set the project with gcloud config set project PROJECT_ID, and push again. The second push succeeded.

If the push fails: checklist

  1. Active account: run gcloud auth list. If no account is active, run gcloud auth login.
  2. Project: run gcloud config list and check the project value. If it is empty, run gcloud config set project PROJECT_ID.
  3. Docker credentials: run gcloud auth configure-docker with your registry host, for example asia-northeast3-docker.pkg.dev.
  4. Permissions: if you still get unauthorized or permission denied, check in IAM that your account has the Artifact Registry Writer role (roles/artifactregistry.writer).
  5. Repository exists: Artifact Registry needs the repository to be created before the first push, and the REPO name in the image tag must match it.

Verify the upload

List the images in the repository:

gcloud artifacts docker images list asia-northeast3-docker.pkg.dev/PROJECT_ID/REPO

Originally written in 2020 in Korean. This English version was added in November 2026.

[English] Android TableLayout: Make Button Widths Equal with layout_weight

Korean original: [TIP] Table Layout 의 Width 균일하게 설정

The problem

You have several buttons in a TableRow and want every button to have exactly the same width, no matter how large each image or label is.

The fix

Give each child view a width of 0dp and a layout_weight of 1.

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TableRow>
<ImageButton
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@drawable/plusbtn" />
<ImageButton
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@drawable/plusbtn" />
</TableRow>
</TableLayout>

Why this works

TableRow and LinearLayout measure each child first and then hand out the remaining space in proportion to layout_weight. With a width of 0dp the starting size of every child is zero, so the whole row width is shared by weight (here 1 : 1). With wrap_content, the content size is counted first, so widths can end up different.

How to check

  • Add more buttons or use images of different sizes. The widths should stay equal.
  • Set one weight to 2. That button should become twice as wide as the others.

Modern alternatives

  • fill_parent was replaced by match_parent in API level 8. The old dip unit is the same as dp.
  • For a single row, a horizontal LinearLayout with 0dp widths and weight 1 works the same way.
  • For a grid, consider GridLayout (API 21+) with layout_columnWeight, or a ConstraintLayout horizontal chain (spread) with 0dp widths.

Originally written in 2011 in Korean. This English version was added in November 2026.

2025년 12월 14일 일요일

Installation for Qwen3-vl:4b on Windows 11

Windows 11에서 Ollama를 설치하고 Qwen3-VL 4B 모델을 돌려본 과정을 정리해 둔다. 텍스트뿐 아니라 이미지도 같이 넣을 수 있는 모델이다. 아래 캡처는 설치할 때 실제로 나온 화면들이다.

먼저 알아둘 것

  • OS는 Windows 10 22H2 이상이면 된다.
  • NVIDIA 그래픽카드를 쓴다면 드라이버가 551.61 이상이어야 한다. GPU가 없어도 CPU로 돌아가긴 하는데 느릴 수 있다.
  • Ollama 설치에 4GB 정도, qwen3-vl:4b 모델 파일이 3.3GB라서 여유 공간을 넉넉히 잡아두는 게 좋다.
  • Qwen3-VL은 Ollama 0.12.7 이상에서 동작한다. 최신 버전을 받으면 문제 없다.

설치 순서 (아래 캡처 순서와 같다)

  1. https://ollama.com 에서 Windows용 OllamaSetup.exe를 받아 실행하고 Install을 누른다. 관리자 권한은 필요 없고 사용자 계정 폴더에 설치된다.
  2. 파일 압축을 푸는 진행 화면이 나오는데 끝날 때까지 기다린다.
  3. 중간에 Microsoft Visual C++ 2015-2022 Redistributable(x64) 설치 창이 뜨면 그대로 진행한다.
  4. 설치가 끝나면 Ollama 앱이 뜬다. 채팅창 오른쪽 아래 모델 선택에서 qwen3-vl:4b를 고르고 아무 메시지나 보내면, 처음 한 번 모델을 내려받은 뒤 대화할 수 있다.

 

https://ollama.com/





















터미널에서 실행하기

PowerShell이나 cmd에서 아래 명령으로도 같은 모델을 쓸 수 있다.

ollama run qwen3-vl:4b

이미지를 넣을 때는 질문 뒤에 이미지 파일 경로를 같이 적어주면 된다.

모델 크기

Ollama 라이브러리 기준으로 2b는 1.9GB, 4b는 3.3GB, 8b는 6.2GB, 30b는 20GB 정도이고 컨텍스트는 전부 256K다. 메모리가 넉넉하지 않으면 2b나 4b가 무난하다.

참고

  • Ollama 서버는 http://localhost:11434 에서 동작해서 API로도 호출할 수 있다.
  • 모델 파일은 기본적으로 %HOMEPATH%\.ollama 에 저장된다. 위치를 바꾸려면 사용자 환경변수 OLLAMA_MODELS를 지정하면 된다.
  • 로그는 %LOCALAPPDATA%\Ollama 폴더의 app.log, server.log에서 볼 수 있다.

용량과 최소 버전은 2026년 10월 기준 Ollama 공식 페이지 내용이라 이후에 바뀔 수 있다.

2023년 3월 13일 월요일

[Docker] docker build RUN apk update Error

Step 3/7 : RUN apk update

 ---> Running in 2fdba320b542

fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz

fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz

ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.9/main: temporary error (try again later)

WARNING: Ignoring APKINDEX.b89edf6e.tar.gz: No such file or directory

ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.9/community: temporary error (try again later)

WARNING: Ignoring APKINDEX.737f7e01.tar.gz: No such file or directory



$ docker build --tag shlee/springboot-cicd:latest --no-cache --network=host .

 --> Add "--network=host" .. 


[업데이트 2026-10]

  • 컨테이너 안에서 apk 인덱스를 받아오지 못해서 나는 오류다. 빌드하는 동안 컨테이너 네트워크(DNS 포함)가 막혀 있을 때 자주 나고, --network=host를 주면 호스트 네트워크를 그대로 써서 해결되는 경우가 많다.
  • 예제의 Alpine v3.9는 오래된 버전이라 저장소 주소가 더 이상 응답하지 않을 수 있다. 베이스 이미지를 최신 Alpine 태그로 올려서 다시 빌드해 보는 것도 방법이다.

2023년 3월 10일 금요일

Nexus Installation on Ubuntu

$ mkdir nexus

$ wget https://download.sonatype.com/nexus/3/nexus-3.39.0-01-unix.tar.gz

$ sudo apt-get update

$ sudo apt install openjdk-8-jdk-headless

$ cd nexus/bin


- install & startup : nexus run

- startup : nexus start

- stop : nexus stop



[업데이트 2026-10]

  • 위의 3.39.0 + JDK 8 조합은 오래된 구성이다. 최신 버전은 Sonatype 다운로드 페이지에서 확인하고, 필요한 Java 버전은 받은 버전의 요구사항을 따른다. 최근 배포본에는 JDK가 같이 들어 있는 경우가 있어서 따로 설치하지 않아도 될 수 있다.
  • root가 아닌 별도 사용자(예: nexus)로 실행하는 것이 좋다.
  • 기동하면 기본 접속 주소는 http://localhost:8081 이다.
  • 초기 admin 비밀번호는 sonatype-work/nexus3/admin.password 파일에 들어 있다. 처음 로그인한 뒤 바꿔준다.