Deployment Guide¶
2026-08-12 — 배포 방식이 바뀌었습니다. 이미지는 Mac 에서 빌드해 서버로 밀어 넣고, 서버는 절대 빌드하지 않습니다. 이 문서 최상단 두 절이 현재 기준이며, 그 아래
production_deploy.sh관련 내용은 **과거 기록**입니다 (해당 스크립트는 더 이상 쓰지 않습니다).
현재 배포 방식 (Mac 빌드 + 이미지 전송)¶
./scripts/deploy.sh staging # → CT 133 (ext-stg-project-c), branch develop
./scripts/deploy.sh production # → infra-production, branch main
흐름: 변경된 서비스만 linux/amd64 로 빌드 → 정말 amd64 인지 검증 →
docker save | ssh docker load → 서버에 이미지가 있는지 검증 →
git pull + up -d(--build 없음) → 빌드 캐시는 Mac 에서 정리.
Odoo 모듈 업그레이드는 **별도 단계**입니다. 배포가 대신 해주지 않습니다.
ENV=staging ./scripts/deploy.sh migrate # 기본 MODULES=jdx_crm_saas
ENV=production ./scripts/deploy.sh migrate
사전 pg_dump → crm_lead_sns_trigger / contact_update_trigger 비활성화 →
업그레이드 → 재활성화 → Odoo 재기동 순입니다. 두 트리거는 행마다 발화하므로
켜 둔 채 업그레이드하면 SNS 알림이 폭주합니다.
왜 바꿨나: 서버 빌드는 스테이징 CT 133 에 19GB 의 빌드 캐시를 쌓아 40G 디스크를 84% 까지 채웠고(2026-08-12), 프로덕션은 배포마다 2GB Odoo 이미지를 직접 빌드하고 있었습니다.
효과 (CT 133 실측): 전환 배포 직후 서버 빌드 캐시가 5.708GB 에서 단 1바이트도 늘지 않았습니다(54개 항목 그대로). 이것이 서버가 빌드하지 않았다는 직접 증거입니다. 이후 구 캐시를 제거해 0B, 디스크 38% → 30%.
규칙¶
- 서버에서
docker compose up을 직접 실행하지 마십시오 —~/jdx/deploy.sh가 Infisical 시크릿을 주입합니다 - 서버에
--build를 넘기지 마십시오. 이미지가 없으면 보내십시오.deploy.sh는 서버가 몰래 빌드하게 두느니 중단합니다 extra-addons/는 bind-mount 라git pull만으로 서버에 도달합니다 — 이미지 재빌드가 필요 없습니다. 다만 모델·뷰 변경은 여전히migrate가 필요합니다- 이 Mac 은 arm64 라 amd64 빌드는 에뮬레이션입니다. 마커가 없는 첫 배포는 전부 빌드하므로 느립니다
롤백 (서버 빌드로 되돌리기)¶
build: 섹션을 compose 에 남겨 뒀으므로 이 경로는 항상 살아 있습니다.
전환 중 겪은 빌드 실패 4건 (2026-08-12)¶
스테이징 첫 배포는 네 번 만에 성공했습니다. 넷 다 **서버에서 빌드할 때는 드러나지 않던 문제**입니다. 서버는 매번 깨끗한 체크아웃에서 빌드하지만 개발 머신에는 로컬 산출물이 있고, 아키텍처도 다릅니다. 전환의 비용이 여기 있었습니다.
① ai-visualizer — apt 캐시 마운트 id 충돌¶
한 RUN 안에서 /var/cache/apt 와 /var/lib/apt 에 같은 cache id 를
걸고 있었습니다. 두 경로가 같은 디렉토리가 되어 apt 가 자기 lists 를 잠그지
못합니다.
수정: ai-visualizer/docker/Dockerfile — id 를 apt-cache-* / apt-lib-*
로 분리. 경로가 겹치는 것 자체가 잘못이므로 서버에서 통과했더라도 버그입니다.
② nextjs — .dockerignore 부재로 로컬 node_modules 충돌¶
Dockerfile 이 COPY --from=deps /app/node_modules 다음에 COPY . . 를 합니다.
.dockerignore 가 없어 로컬 node_modules 1.1GB 가 그 위에 덮이는데,
pnpm 의 심볼릭 트리와 실제 디렉토리가 충돌합니다. 서버에는 node_modules 가
없으니 평생 드러나지 않던 문제입니다. 덤으로 .next 491MB 까지 컨텍스트로
올라가고 있었습니다.
수정: nextjs-frontend/.dockerignore 신설.
③ fastapi — 같은 원인, 조용한 버전¶
fastapi-backend/Dockerfile 도 COPY --chown=appuser:appuser . . 로 끝납니다.
빌드는 성공하지만 arm64 로 만든 .venv 245MB 와 mypy 캐시 68MB 가 amd64
이미지 안으로 그대로 들어가고 있었습니다. 실패하지 않았기에 더 위험합니다.
수정: fastapi-backend/.dockerignore 신설.
④ nextjs — arm64 이미지가 만들어짐 (가드가 잡음)¶
DOCKER_DEFAULT_PLATFORM=linux/amd64 를 줬는데도 compose 가 nextjs 에만
platform 을 넣지 않았습니다. docker compose build --print 로 확인한 결과
fastapi·odoo·docs·landing-mb·ai-visualizer 에는 platforms: [linux/amd64] 가
들어가고 nextjs 에는 없었습니다(nextjs 는 profiles 를 가지고 있어 다른 경로를
탑니다).
deploy.sh 의 verify_amd64 가 ship 직전에 잡아서 사고는 없었지만, 가드에
기대는 것은 설계가 아닙니다.
수정: docker-compose.staging.yml / docker-compose.prod.yml 의 빌드 대상
13곳에 platforms: [linux/amd64] 명시. 서버가 amd64 라는 사실은 환경변수가
아니라 compose 에 적혀 있어야 합니다.
⑤ (실패는 아님) migrate 의 트리거 복구 트랩이 죽어 있었음¶
배포 도중 발견했습니다. 트리거 재활성화 안전망을 trap ... ERR 로 걸어
뒀는데, 이 스크립트의 모든 실패 경로는 ... || err "..." 를 거치고
err() 는 exit 를 호출합니다. exit 는 ERR 트랩을 발화시키지 않습니다
(게다가 || 자체가 ERR 을 억제합니다). 즉 업그레이드가 실패하면 트리거가
꺼진 채로 남고, CRM 쓰기가 SNS 에 도달하지 않는데 아무 신호도 없었을
것입니다.
수정: EXIT 트랩 + TRIGGERS_DISABLED 플래그로 교체.
배운 것¶
- 로컬 빌드 산출물은 빌드 컨텍스트의 일부다.
COPY . .로 끝나는 Dockerfile 에.dockerignore가 없으면, 개발 머신에서 빌드하는 순간 드러납니다. 새 서비스를 추가할 때 함께 확인하십시오 DOCKER_DEFAULT_PLATFORM을 신뢰하지 마십시오. compose 가 서비스별로 조용히 무시합니다.docker compose build --print로 실제 bake 정의를 보면 확인할 수 있습니다- 셸 안전망은 실제로 발화하는지 확인하십시오.
set -e+ERR트랩은|| err패턴과 함께 쓰면 죽은 코드가 됩니다
이하 과거 기록 — production_deploy.sh 시대¶
⚠️ 아래는 더 이상 현행이 아닙니다.
production_deploy.sh는 쓰지 않고, SSL 은 Traefik 이, 시크릿은 Infisical 이 담당하며, 서버에.env를 두지 않습니다. 당시 절차를 기록으로만 남깁니다.
Deployment Script Overview¶
All deployment operations use ./production_deploy.sh, which reads configuration from .env.
# View current configuration
./production_deploy.sh config
# View all available commands
./production_deploy.sh help
Quick Reference¶
| Command | Description |
|---|---|
deploy |
Full deployment (backup, pull, build, update modules) |
update |
Quick update (backup, pull, rebuild, update modules) |
start |
Start all services |
stop |
Stop all services |
restart |
Restart all services |
status |
Check service status |
logs |
View logs |
init-ssl |
Full SSL setup |
config |
Show current configuration |
Environments¶
| Environment | Config | Database | Exposed Ports |
|---|---|---|---|
| Test | docker-compose.test.yml |
Local PostgreSQL | All |
| Production | docker-compose.prod.yml |
AWS RDS | Only 80, 443 |
Test Environment Deployment¶
Prerequisites¶
- Docker and Docker Compose installed
- Git access to repository
- Ports available: 80, 443, 8016, 8000, 8001, 8002
Steps¶
# 1. Clone repository
git clone git@github.com:yourorg/odoo15-production.git
cd odoo15-production
# 2. Configure environment
cp .env.example .env
nano .env # Verify settings
# 3. Verify configuration
./production_deploy.sh config
# 4. Start services
./production_deploy.sh start
# 5. Verify services
./production_deploy.sh health
Access URLs (Test)¶
| Service | URL |
|---|---|
| Odoo | http://localhost:8016 |
| Landing Page | http://localhost |
| PWA | http://localhost:8000 |
| Docs | http://localhost:8002 |
Production Environment Deployment¶
Prerequisites¶
- AWS EC2/Lightsail instance
- AWS RDS PostgreSQL instance
- Domain names configured (DNS pointing to server)
- Ports 80, 443 open in security group
Step 1: Initial Setup¶
# SSH to production server
ssh user@production-server
# Clone repository
git clone git@github.com:yourorg/odoo15-production.git
cd odoo15-production
# Create production .env
cp .env.example .env
nano .env
Step 2: Configure .env for Production¶
# Environment
ENVIRONMENT=production
GIT_BRANCH=main
# Docker Compose (use production overlay)
COMPOSE_FILE=docker-compose.yml:docker-compose.prod.yml
# Database (AWS RDS)
DB_HOST=your-db.xxxxx.us-east-1.rds.amazonaws.com
DB_PORT=5432
DB_NAME=odoo_production
DB_USER=odoo_admin
DB_PASSWORD=<strong-password>
# Domains
DOMAIN_LANDING=yourcompany.com
DOMAIN_ERP=erp.yourcompany.com
DOMAIN_PWA=pwa.yourcompany.com
DOMAIN_DOCS=docs.yourcompany.com
# SSL
LETSENCRYPT_EMAIL=admin@yourcompany.com
Step 3: SSL Certificate Setup¶
This runs the following automatically:
- Creates dummy self-signed certificates
- Generates nginx config with your domains
- Starts nginx with HTTPS
- Requests real certificates from Let's Encrypt
Manual SSL Steps (if needed)¶
# Step-by-step SSL setup
./production_deploy.sh init-ssl-dummy # 1. Create dummy certs
./production_deploy.sh configure-nginx # 2. Generate nginx config
./production_deploy.sh restart # 3. Start services
./production_deploy.sh init-ssl-request # 4. Get real certs
Step 4: Deploy Application¶
# Start all services
./production_deploy.sh start
# Or full deployment (includes module updates)
./production_deploy.sh deploy
Step 5: Setup SSL Auto-Renewal¶
# Add to root crontab
sudo crontab -e
# Add this line (runs every Sunday at 3 AM):
0 3 * * 0 /path/to/odoo15-production/scripts/ssl-renew-cron.sh >> /var/log/ssl-renew.log 2>&1
Deployment Commands¶
Full Deployment¶
Use for major updates or initial deployment:
This performs:
- Check
.envexists - Create backup
- Pull latest code from
GIT_BRANCH - Stop containers
- Build Docker images
- Start services
- Update all Odoo modules
- Run health checks
Quick Update¶
Use for routine updates:
This performs:
- Check
.envexists - Create backup
- Pull latest code
- Rebuild containers (if needed)
- Restart services
- Update Odoo modules
- Run health checks
Update Single Module¶
Service Management¶
Start/Stop/Restart¶
# All services
./production_deploy.sh start
./production_deploy.sh stop
./production_deploy.sh restart
# Single service
./production_deploy.sh restart-service odoo
./production_deploy.sh restart-service nginx
./production_deploy.sh restart-service pwa
View Status¶
View Logs¶
# All services
./production_deploy.sh logs
# Specific service
./production_deploy.sh logs odoo
./production_deploy.sh logs nginx
Rebuild Services¶
# Rebuild all
./production_deploy.sh rebuild-all
# Rebuild specific service
./production_deploy.sh rebuild-pwa
./production_deploy.sh rebuild-docs
./production_deploy.sh rebuild-landing
SSL Commands¶
| Command | Description |
|---|---|
init-ssl |
Full SSL setup (dummy → configure → request) |
init-ssl-dummy |
Create self-signed dummy certificates |
init-ssl-request |
Request real certificates from Let's Encrypt |
renew-ssl |
Renew existing certificates |
configure-nginx |
Generate nginx config with domains from .env |
SSL Certificate Locations¶
nginx/ssl/
└── live/
└── yourcompany.com/
├── fullchain.pem # Certificate chain
└── privkey.pem # Private key
Manual Certificate Renewal¶
Database Management¶
Access Database Shell¶
Odoo Shell¶
Install/Update Module¶
# Via deploy script
./production_deploy.sh update-module module_name
# Or directly
docker compose exec odoo odoo -u module_name --stop-after-init -d $DB_NAME
Backup & Recovery¶
Create Backup¶
This runs ./scripts/backup.sh full which backs up:
- Database (pg_dump)
- Filestore (./odoo-data)
- Configuration files
Restore from Backup¶
Deployment Checklist¶
Pre-Deployment¶
- Code reviewed and tested
-
.envconfigured correctly - Database backup taken (
./production_deploy.sh backup) - Team notified
Deployment¶
- Run deployment command
- Verify health checks pass
- Check logs for errors
Post-Deployment¶
- Test critical functionality
- Monitor logs for 15 minutes
- Verify backups running
- Update documentation if needed
Rollback Procedure¶
If deployment fails:
# 1. Stop services
./production_deploy.sh stop
# 2. Restore previous code
git checkout <previous-tag>
# 3. Restore database if needed
./scripts/restore.sh <backup-file>
# 4. Start services
./production_deploy.sh start
# 5. Verify rollback
./production_deploy.sh health
Troubleshooting¶
View Configuration¶
Service Won't Start¶
# Check logs
./production_deploy.sh logs <service>
# Check container status
./production_deploy.sh status
# Force recreate
docker compose up -d --force-recreate <service>
Database Connection Issues¶
# Test database connection
docker compose exec db pg_isready -U odoo
# Check database exists
docker compose exec db psql -U odoo -l
SSL Certificate Issues¶
# Test nginx config
docker compose exec nginx nginx -t
# Check certificate files exist
ls -la nginx/ssl/live/*/
# Request new certificates
./production_deploy.sh init-ssl-request
Nginx Issues¶
# Test config
docker compose exec nginx nginx -t
# Reload config
docker compose exec nginx nginx -s reload
# View nginx logs
./production_deploy.sh logs nginx
File Reference¶
| File | Purpose |
|---|---|
production_deploy.sh |
Main deployment script |
scripts/ssl-init.sh |
SSL certificate management |
scripts/ssl-renew-cron.sh |
SSL auto-renewal (cron) |
scripts/backup.sh |
Backup script |
scripts/restore.sh |
Restore script |
scripts/health-check.sh |
Health check script |
nginx/conf.d/default.conf |
Active nginx config |
nginx/conf.d/production.conf.template |
Production HTTPS template |