아이패드로 개발하는 방법... 제가 알아왔습니다

이것만 있으면 집, 카페, 회사(?!) 언제 어디서든 동일한 환경으로 개발할 수 있다
심지어 우리에게 익숙한 VSCode로..!
모든건 서버에 두고 다니니 내가 필요한 것이라곤 디스플레이와 키보드만 있으면 된다
해보니 스마트폰 + 블투 키보드 조합도 되는데, 개인적으론 화면이 너무 작으면 답답했다
(급할 때 빌드 명령어 정도?)
(노트북을 제외하면) 아이패드로 개발하기 딱이었다 (사이즈, 무게, 휴대성 전부)
이 방식의 가장 큰 장점은 아무리 고물 노트북이라도 웹 브라우저를 원활히 돌리는 성능만 있으면 고사양 서버의 성능을 온전히 쓸 수 있다는 것이다!
일례로 단순히 재귀로 숫자 print하면 3000번대쯤 터지는 15년도 삼성 노트북이 있는데 이 친구도 곧잘 됐다 ㅋ
(10년 일했으니 놔달라고...? 형이랑 15년 더 같이 일해야지.. 일어나...)
1. Code-Server에 대한 소개
Code-Server란?

공홈 설명에 의하면 '서버에 실행시켜둔 VSCode를 어떤 기기, 어디서든 웹 브라우저로 접속할 수 있게 해주는 것'이다
https://github.com/coder/code-server
GitHub - coder/code-server: VS Code in the browser
VS Code in the browser. Contribute to coder/code-server development by creating an account on GitHub.
github.com
뭐가 좋은데?
말 그대로 서버에 띄워두기만 하면, 언제 어디서든 접근만 되면 '늘 동일한 환경'으로 '편하게' 개발할 수 있다
그리고 접속하는 클라이언트의 컴퓨터가 꼭 좋지 않아도 된다
어차피 개발이 진행되는 동안 리소스는 전부 서버의 것을 가져다 쓰기 때문이다
- 좀 더 정확히 하면 위 내용은 VSCode의 장점보단 '서버'의 장점이 맞겠다.
- 다만 VSCode는 단순히 터미널보다 직관적이고 확장 프로그램도 다양하니 굉장히 편리하겠죠?
- 아무래도 들여쓰기 확인 및 편집이 쉽지 않은 터미널보단 익스텐션까지 있는 VSCode가 훨씬 선호될 것이다

즉, 갑작스레 하와이 출장을 가서 10년 지난 문서용 노트북으로 AI를 개발해야할 때 위 환경이 갖춰져있다면?
(눈물부터 흘려야겠지) 환경 세팅하는 것에 시간 낭비하지 않고 바로 진행할 수 있다는 것이다
2. 서버 vs 컨테이너 (설치 위치)
- 각자의 일장일단이 있다
- 서버에 냅다 설치 : 모든 파일에 접근 가능하여 편리함. But, 보안상 위험
- 컨테이너에 설치 : 독립적 환경이라 보안이 비교적 높음. But, 모든 파일에 접근 불가
참고로 나는 '서버에 설치 및 실행 - Nginx 컨테이너로 리버스 프록시'를 꿈꿨으나 안되더라...

쉽게 말해 방패(nginx)를 뚫고 넘어가야 서버의 품에 있는 code-server에 접근할 수 있음
(자세한 과정은 맨 아래 삽질 파트에 적어두었음)
3-1. 서버에 설치하기
- 서버 설치는 굉장히 쉽다
- 그냥 Code-Server 설치하고 띄우고 웹 URL에 서버 주소 + 포트번호 조합으로 들어가면 끝!
1). Code-Server 설치하기
curl -fsSL https://code-server.dev/install.sh | sh
2) 디렉토리 생성 (Code-Server 설정 저장할 디렉토리)
mkdir -p ~/.config/code-server
3) 설정 파일 생성
- 포트번호는 8080을 사용하는데, 다른 프로세스가 점유하고 있다면 바꿔야할 수 있음
- 127.0.0.1은 자기자신(localhost)를 의미한다
- 즉, 루프백(자기자신을 콜하는 것)하겠다는 의미이고, 외부에서 접근이 불가능하다
# 템플릿
cat > ~/.config/code-server/config.yaml << EOF
bind-addr: 127.0.0.1:8080
auth: password
password: {VSCode 접속 시 입력할 비밀번호}
cert: false
EOF
# 예시
cat > ~/.config/code-server/config.yaml << EOF
bind-addr: 127.0.0.1:20202
auth: password
password: ratatou2.tistory
cert: false
EOF
4) 실행하기
- & 옵션은 백그라운드에서 실행하겠다는 의미
code-server &
- 이제 서버 IP 입력하고 창을 열거나, GUI가 있는 Linux 서버라면 localhost:포트번호를 치고 들어가면 사용할 수 있다

- 실행하면 위에서 설정한 패스워드를 입력하는 창이 뜬다
- 입력하고 접속한 뒤 일반 VSCode처럼 디렉토리를 설정해주면 개발을 이어갈 수 있다

3-2. Docker 컨테이너로 띄우기
- 기본 환경이 세팅되어있다고 가정하고 진행한다 (Docker + nginx)
- 다만 Code-Server는 별도의 추가 컨테이너(e.g. SQL)가 필요하진 않다
1) docker-compose.yml
템플릿
services:
code-server:
image: codercom/code-server:latest
container_name: code-server
restart: unless-stopped
# 포트 설정
ports:
- "8080:8080"
# 환경변수
environment:
- PASSWORD=mypassword123 # 웹 접속 비밀번호
- SUDO_PASSWORD=sudopass456 # 컨테이너 내 sudo 비밀번호
- DEFAULT_WORKSPACE=/workspace
# 볼륨 마운트
volumes:
# 프로젝트 파일 저장소
- ./workspace:/workspace
# VS Code 설정 및 익스텐션 보존
- ./vscode-config:/home/coder/.local/share/code-server
# 선택사항: 도커 소켓 마운트 (컨테이너 내에서 도커 명령어 사용)
# - /var/run/docker.sock:/var/run/docker.sock
# 사용자 설정 (선택사항)
user: "1000:1000"
예시
code-server:
image: linuxserver/code-server
container_name: code-server-container
restart: always
environment:
- PUID=1000 # `id -u` 명령어로 확인한 본인 ID
- PGID=1000 # `id -g` 명령어로 확인한 본인 GID
- TZ=Asia/Seoul
- PASSWORD=${VSCODE_PASSWORD}
volumes:
- ./code-server/config:/config # code-server 설정파일 저장 경로
- /home/temp/Project/dev:/home/coder/workspace # 절대 경로 사용
networks:
- app_network
2) Certbot으로 SSL 인증받기
- 서버의 VSCode에 접근하기 위한 주소(DNS)를 쓰게 될텐데 그것을 위해 SSL 인증서를 발급 받았다
- 진행하는 방법은 아래 포스팅 참조
https://ratatou2.tistory.com/85
Certbot으로 SSL 인증서 발급받기 (feat. 부셔버릴 통신사 공유기)
통신사 공유기는 못 들어오십니다통신사 공유기세요? 그거 안돼요 버리세요그래도 하시겠다구요? 그럼 따라하시다가 안되면 일단 통신사 공유기 탓입니다공유기 바꾸시는게 정신건강에 좋아
ratatou2.tistory.com
3) nginx.conf 설정 추가
- nginx를 안쓰고 있다면 이 과정은 필요없다
- 내 서버는 nginx를 리버스 프록시로 쓰고 있어서 필요했음
- 서버에 들어온 요청을 적합한 서비스(e.g. 컨테이너)에 전달하는 용도이다
# Visual Studio Code 전용 DNS (test-code-url.duckdns.org)
server {
listen 443 ssl;
http2 on;
server_name test-code-url.duckdns.org; # Code Server 전용 도메인
# 보안 공격 요청 거절
include snippets/deny-sensitive-files.conf;
include snippets/block-malicious-urls.conf;
# SSL 인증서 (Let's Encrypt로 생성 필요)
ssl_certificate /etc/letsencrypt/live/test-code-url.duckdns.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/test-code-url.duckdns.org/privkey.pem;
# SSL 설정
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Code Server 전용 보안 헤더 (CORS 설정 제거)
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always; # Code Server는 같은 도메인만
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# Code Server 프록시 설정
location / {
proxy_pass http://172.17.0.1:23618; # Code Server 로컬 포트
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 지원 (VS Code 실시간 기능에 필수!)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 타임아웃 설정 (대용량 파일 처리)
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 버퍼링 비활성화 (실시간 응답)
proxy_buffering off;
proxy_request_buffering off;
# 파일 업로드 크기 제한
client_max_body_size 500M;
}
# 정적 리소스 캐싱 (선택사항)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
proxy_pass http://172.17.0.1:23618;
proxy_set_header Host $host;
expires 30d;
add_header Cache-Control "public, no-transform";
}
}
4) Code-Server 컨테이너 실행하기
- docker-compose.yml에 추가해뒀으니 컨테이너를 실행하면 된다
docker compose down
docker compoes up -d
유의사항
1) 외부에서 접근할 때, DNS를 쓸 것이라면 항상 certbot 인증이 우선되어야한다!!
- 이거 진짜 너무 당연한데 까먹기 쉽다
- 이번에도 아래 세가지 중 하나 때문에 시간을 좀 허비했음
- Linux 서버 SSD를 교체하면서 마이그레이션 했는데 이로 인해 디렉토리가 변경되었음...
- DNS로 서버에 접근할거면 아래 세가지는 꼭 확인해야한다
1) certbot 인증 먼저 받고
2) 인증 pem 키들을 nginx가 인식하는 디렉토리에 뒀는지
3) 컨테이너 볼륨 마운트 설정에 의해 해당 디렉토리가 비워져있진 않은지
특히 3번 같은 경우
- 볼륨 디렉토리를 미리 만들어두면, 컨테이너 내부의 디렉토리 안의 파일들을 복사해두는 것이 아니라서, 비어있는 디렉토리가 컨테이너 내부에 마운트된다
- 반면에 로컬에 볼륨 디렉토리가 없으면, 도커가 컨테이너 내부 파일 복사하면서 만들어준다
- 내 경험상 빈 디렉토리를 마운트하는 경우가 좀 위험할만한 경우가 많았음
- 예를들어 비어있는 디렉토리를 log 디렉토리로 지정 후 마운트
- 컨테이너 내부의 마운트된 디렉토리에 로그를 저장하여 로컬에서 확인
=> 정상 동작 (컨테이너 내부 파일을 로컬에서 볼 수 있음)
- 반면에 비어있는 디렉토리를 컨테이너 내부 설정 디렉토리인 '/var/config'로 지정 후 마운트
- 컨테이너 내부의 설정 파일이 저장되어 있는 '/var/config' 디렉토리가 빈 디렉토리로 마운트되면서 대체됨
=> 에러 발생 (컨테이너 실행에 필요한 설정 파일들의 부재로 인한 에러)
단, 도커의 에러가 아닌, 컨테이너에서 실행되는 앱의 에러임을 주의해야한다
쉽게 말해 실행에 필수적인 파일이 없어서 동작 안하는 셈
2) .env가 적용 안될 때
- 대개 이럴 땐 열에 아홉, 컨테이너에서 물고 있는 .env 데이터가 갱신이 안되어있다
- 이는 Docker Compose가 값을 딱 한번만 읽어오기 때문이다 (컨테이너를 처음 생성(create)할 때)
- 따라서 아예 컨테이너를 새로 만드는 것이 좋다
- 단순히 재시작(restart)을 하면 기존의 환경변수 그대로 사용하므로 강제로 재생성을 추천하는 것이다
- 컨테이너 재생성은 아래 세가지 명령어 중 하나 하면 됨
# [옵션 1] 강제 재생성 (추천)
docker compose up -d --force-recreate
# [옵션 2] 완전 삭제 후 재생성
docker compose down
docker compose up -d
# [옵션 3] 특정 서비스만 재생성
docker compose up -d --force-recreate code-server
- 이런 일이 일어나는 이유는 Docker의 불변성(Immutable) 때문이다 (= 컨테이너는 생성 후에 환경변수 변경 불가)
3) linuxserver 버전을 선택한 이유 (feat. 'codercom' vs 'linuxserver')
- 두 버전 차이는 대체 무엇인고 했더니만, 파일 권한 관련 차이가 있었다
- 조사 끝에 나는 linuxserver 버전의 이미지를 받아서 썼다
codercom
- codercom은 code-server 개발팀이 직접 제공
- 따라서 매번 가장 빠른 업데이트 환경을 접할 수 있음
- 가장 순수한 code-server 환경
- 파일 권한 이슈가 조금 있음
- 컨테이너 내부는 알다시피 권한 문제가 조금 복잡하다
- 로컬(서버)의 유저 ID는 컨테이너 내부의 유저 ID와 '권한이 다르다'
- 평소엔 컨테이너의 서비스를 이용하고자 하는 것이 대다수라 큰 문제가 아니지만 Code-Server는 조금 다르다
- 우리는 이 서비스를 통해서 파일을 생성, 편집, 삭제하게 되기 때문이다
- 결과적으로 code-server 컨테이너 내부에서 유저 ID의 권한으로 생성한 .py 파일이 로컬(서버)의 유저와 권한이 달라 소유자가 꼬이거나 권한 오류로 인해 파일 실행이 막히는 등 '내가 만든 파일인데, 내 파일이 아니라서 서버에서 실행을 못함' 이런 그지발싸개같은 경우가 생길 수가 있다
- 저도 알고 싶지 않았어요...
linuxserver
- 이름부터 극호감 (리눅스 서버 전용..? 어맛..!)
- 파일 권한 문제를 가장 쉽게 해결해뒀다고 한다
- PUID(사용자 ID), PGID(그룹 ID) 환경변수만 세팅해두면 컨테이너 내부의 프로세스가 해당 사용자의 권한으로 시랳ㅇ된다
- 이말인 즉, 로컬(호스트)에 마운트한 디렉토리의 파일 권한 문제를 아주 간단하게 해결할 수 있는 것!!
- Nextcloud 오픈소스
삽질 모음집
1) 별z-랄을 다해도 서버에 설치해서 Nginx로 접근하는건 안되더라

일단 이 작고 작은 서버에 'VSCode 띄우기'라는 사이드 프로젝트(?!)가 SSD 마이그레이션 이후 처음이었다
깔끔하게 정리하겠다고 경로나 파일 위치가 바뀐게 좀 있어서 자잘하게 마주한 에러가 정말 많았음
기억난 것만 적어도 대충 6개...?
1. 인증서 경로 문제 - certbot이 인증서를 저장하는 위치와 nginx가 읽는 위치가 달랐던 문제 (해결)
2. 인증서 파일 구조 문제 - live 디렉토리의 파일이 심볼릭 링크가 아니었던 문제 (해결)
3. Nginx 설정 누락 문제 - code-server.duckdns.org를 위한 server 블록이 없었던 문제 (해결)
4. 컨테이너-호스트 간 네트워크 문제 - nginx 컨테이너가 호스트의 code-server에 접근하지 못했던 upstream timed out 문제 (해결)
5. `code-server` 실행 설정 문제 - bind-addr 설정이 잘못되어 nginx의 접속을 받지 못했던 문제 (해결)
6. `code-server` 포트 충돌 문제 - 기존 프로세스가 포트를 점유하고 있던 EADDRINUSE 문제 (해결)
제일 문제가 되었던건.. 소제목처럼 서버(로컬)에 code-server 설치 후 nginx의 리버스 프록시로 접근하는 것
명확한 원인은 모르지만 내가 6시간 삽질하면서 알게된 것은 '도커의 네트워크 정책'가 문제가 됐단 것이다

- 좀 더 정확히 말하면 컨테이너 내의 localhost는 로컬(서버)의 localhost가 아니다
- 둘은 bridge 네트워크로 구분되어 있음
- 즉, 로컬(서버)에서 127.0.0.1로 code-server를 돌렸을 시점에, 우리는 nginx 컨테이너에게 아래처럼 요청한다
- '이 주소로 들어오는 건 전부 127.0.0.1(로컬(서버))로 리버스 프록시 해!'
- 근데, nginx는? Docker 컨테이너 내부다
- 컨테이너는?? 독립된 하나의 환경(PC)이다
- 즉, nginx 컨테이너 입장에선 '127.0.0.1로 리버스 프록시 하라고? 아!! 나 자신(nginx 컨테이너)에게 보내라고!!!' 가 된다
- 결국 해당 요청이 nginx 컨테이너 내부에서 메아리 치고 로컬(서버)까지 도달하지 못하니 502 Bad Gate나 토해내며 '뭐 암것도 없고, 도달 못하겠는데요?'가 되는 것이다
그리고 이것을 해결하기 위해 보통 세가지 방식이 있다
1) host.docker.internal 사용하기
2) host 네트워크 모드
3) 컨테이너 IP 직접 지정
간단히 하나씩 설명하면
1) Docker 공식 제공하는 '호스트 접속용 특수 DNS 이름'이다 (근데 난 이걸로 해결이 안됐음 ㅠ)
2) 컨테이너가 호스트의 네트워크를 직접 사용하는 것으로 nginx 컨테이너가 전체 서버 관리자가 된다고 생각하면 된다 (보안상 위험해서 안함 + 컨테이너를 쓰는 의미가 없음)
3) 그냥 로컬 컴퓨터의 실제 IP를 설정하는 것 (= 하드코딩인셈. 나는 이게 '혹여나 IP 바뀔 때마다 수정해야할거다'라고 들려서 안했음)
이마저도 내가 틀렸을 수 있다
내가 하고 싶었던 것은 굉장히 단순했지만 왜 안됐는지 도저히 모르겠음 ㅠ
천천히 다시 하나씩 하면 될지도 모르지만 당장은 삽질을 너무 해서 생각없음...
내가 구상한 방안
(1) nginx 컨테이너에서 서버 외부의 모든 요청(request)을 받고 리버스 프록시를 함
(2) 이 요청이 VSCode DNS로 들어오면 서버의 code-server 주소로 연결
세상 쉬운데 대체 왜 안됨????????
2) 브루트 포스(Brute Force) 공격 어떻게 막을거임?
- 이 방식을 구현해놓고 깨닫게 되었는데, Code-Server... 보안이 허술하다
- 여러분이 이것을 구현하고 URL을 입력해서 접근하면 아래 화면을 보게 된다

- 보다시피 N번 실패하면 뭐 영구히 막히고 이딴거 없다...
- 보자마자 보안 걱정이 됐음.. 걍 온갖 조합으로 될 때까지 다 때려넣으면?
- 이에 대해 보안 방법을 강구하는 중이다
- 잡스러운 Bot의 반복적 요청은 Fail2Ban에서 걸러질테지만, 이 페이지에서 로그인 요청을 여러번 날리는 것은 Ban이 안될 것이라...
- 아마 특정 IP에서 이 DNS 또는 IP로 단시간 내에 5회 이상 같은 요청을 보낸다면 30분 Ban되도록 jail 규칙을 추가해도 될 듯하니 좀 테스트 해봐야겠다
- 보안까지 챙기면 추가 포스팅 후 이 포스팅에 링크 업데이트 예정
3) 아무데서나 개발할 수 있다고, 아무데서나 시키진 않았으면...
친구가 그러더라
'이거 되면 이제 버스나 지하철에서도 개발하면 되겠네??'
같잖은 질문은 짤로 일갈하겠다

'Infra > DevOps' 카테고리의 다른 글
| 오라클 클라우드(OCI) 회원가입 한방에 뚫기 (feat. 30트 끝에 성공...) (0) | 2025.10.04 |
|---|---|
| 서버 모니터링 시스템 구축하기 (feat. Grafana & Prometheus) (0) | 2025.09.22 |
| 서버 보안 설정 시 주의사항과 실제 공격패턴 (feat. Fail2Ban & 내 IP 예외처리) (2) | 2025.08.07 |
| DB 자동 백업 시스템 구축하기 (feat. 모든 DB에 적용 가능) (0) | 2025.07.05 |
| 서버에서 Fail2Ban으로 막힌 IP 해제하기 (feat. 서버에 로컬, 외부망 접속 안됨 해결방법) (0) | 2025.06.14 |
