태그 보관물: docker

OpenProject를 Docker로 설치

프로젝트 관리 도구로 많이 사용되는 Atlassian의 JIRA를 대체하기 위한 오픈소스 프로젝트들 중 가장 인기가 있다는 OpenProject를 회사에서 진행하는 프로젝트를 위해 사용해 보기로 했다.

Docker로 설치하기 준비

Source code clone

OpenProject는 GitHub repo에서 tag가 아닌 branch명을 stable/<version> 형식으로 릴리즈하고 있는 듯 하다. 2026년 4월 현재 가장 최신 버전인 17버전을 다음과 같이 GitHub에서 가져온다.

git clone https://github.com/opf/openproject-docker-compose.git --branch=stable/17 openproject
cd openproject

환경 설정 파일 수정

다운로드 받은 소스에는 환경 설정파일 예제인 .env.example 파일이 있는데, 이 파일 이름을 .env로 변경하고 필요한 사항을 수정해 준다.

  • SECRETE_KEY_BASE에 값을 안내된 명령어 결과 값으로 반영, 단 64 character
    • head /dev/urandom | tr -dc A-Za-z0-9 | head -c 64 ; echo
  • OPENPROJECT_HOST__NAME 값을 localhost에서 IP 혹은 domain이름으로 변경
  • PORT값을 모든 접속을 허용하도록 0.0.0.0:8080으로 변경
  • POSTGRES_VERSION17에서 13으로 변경 (Ubuntu에 설치된 버전)
##
# All environment variables defined here will only apply if you pass them
# to the OpenProject container in docker-compose.yml under x-op-app -> environment.
# For the examples here this is already the case.
#
# Please refer to our documentation to see all possible variables:
#   https://www.openproject.org/docs/installation-and-operations/configuration/environment/
#
TAG=17-slim
OPENPROJECT_HTTPS=false
# Please override the SECRET_KEY_BASE
# Please use a pseudo-random value for this and treat it like a password.
# If you are on Linux you can generate a scret key with the following command
# head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32 ; echo ''
SECRET_KEY_BASE=<명령 수행 결과>
OPENPROJECT_HOST__NAME=<서버 IP>
PORT=0.0.0.0:8080
OPENPROJECT_RAILS__RELATIVE__URL__ROOT=
IMAP_ENABLED=false
DATABASE_URL=postgres://postgres:<database 비밀번호>@db/openproject?pool=20&encoding=unicode&reconnect=true
RAILS_MIN_THREADS=4
RAILS_MAX_THREADS=16
PGDATA="/var/lib/postgresql/data"
OPDATA="/var/openproject/assets"
COLLABORATIVE_SERVER_URL=ws://localhost:8080/hocuspocus
COLLABORATIVE_SERVER_SECRET=secret12345
POSTGRES_VERSION=13

Docker 실행

환경 설정이 끝나면 docker 명령어로 실행 시켜준다.

docker compose up -d

접속확인

이제 웹브라우져에서 ip나 도메인 이름을 입력하고 접속하면 다음과 같이 창이 뜨는데 기본 로그인 id / password는 admin/admin이고 접속하면 비밀번호를 설정하는 화면으로 넘어가게 된다.

HTTPS 적용

HTTPS를 위한 유료 인증서를 사용하고 싶지 않다면 Let’s encrypt를 고려할 텐데, 이 서비스는 public IP를 기준으로 인증서를 발급하는데 제한을 두고 있다. 정확히는 2026년 초부터 IP를 대상으로 발급하는 인증서를 제공하는데 유효기간이 6일로 매우 짧다.

Renew script에 의해 업데이트 되기는 하겠지만, 장기적으로 사용할 것 이라면 도메인을 사용하는게 여러모로 낫다. 대부분의 도메인 등록 기관에서는 기존 도메인이 있다면 추가 비용 없이 서브 도메인 등록이 가능하도록 하고 있으니 이 것을 활용하는 것도 좋을것 같다.

.env 변경

서브 도메인과 IP를 연결한 후에는 .env 파일도 조금 변경해 주어야 한다. 다른 설정들을 유지하고 다음의 세가지 설정을 변경해 준다.

...
OPENPROJECT_HTTPS=true
...
OPENPROJECT_HOST__NAME=<서브 도메인>
PORT=8080
...
  • OPENPROJECT_HTTPS를 true로 설정
  • OPENPROJECT_HOST__NAME을 서브도메인으로 설정
  • PORT를 포트번호만 8080으로 설정

docker-compose.yml 변경

proxy section에 ports 부분에 80번과 함께 https를 위한 443번을 추가해 준다.

  proxy:
    build:
      context: ./proxy
      args:
        APP_HOST: web
    image: openproject/proxy
    <<: *restart_policy
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - web
    networks:
      - frontend

Caddyfile.template 변경

소스코드와 함께 오는 Caddyfile.template은 예제 파일 인 줄 알고 복사해서 Caddyfile로 작성했다가 한참을 해맸는데 proxy/Caddyfile.template 파일을 직접 참조하기 때문에 파일명을 변경하지 않고 직접 수정해 주어야 한다. 다른 설정을다 날리고 아래의 세 줄만 남겼다.

{$OPENPROJECT_HOST__NAME} {
    reverse_proxy web:8080
}

proxy docker 재빌드 및 실행

방화벽이 80번과 443 포트를 열어 주는지 다시한번 확인하고,

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload

proxy docker를 다시 빌드 하고 실행 한다.

docker compose stop proxy
docker compose build --no-cache proxy
docker compose up -d proxy

이메일 설정

OpenProject에서 변경되는 notification을 이메일로 전송받도록 하기 위해서는 메일 설정을 해야 하는데, Postfix로 로컬에 SMTP 서버 설치를 시도 했다가 생각보다 복잡하고 번거로운 스팸 필터 피하는 절차들 때문에 결국 PTR(reverse DNS)를 설정하는 즈음에서 포기하고 말았다.

AWS의 SES같은 메일 서비스는 복잡한 설정 없이도 동작 할 수 있지만 전송량을 생각해보니 한달에 대략 5천원 정도가 나올것 같았다.

이 마저도 아끼려고 Gmail의 SMTP 서버를 사용하도록 등록해 주었다. 인증을 위해서는 “앱 비밀번호”를 생성해서 환경설정 파일에 넣어 주어야 한다.

다만 이 때는 메일을 수신하는 수신자가 회사 시스템으로 부터가 아닌 Gmail에 등록된 내 개인 계정으로 부터 noti를 받게 된다는 사소한 단점이 하나 있다.

뿐만 아니라 일일 사용량에 제한이 있으니 프로젝트 규모가 카서 이메일 전송량이 많은 경우라면 앞서 말한 AWS SES 같은 다른 메일 서비스들을 고려해 보는게 좋겠다.

Multi-container app의 Bad Gateway(502) 문제 디버깅

Intel Training and Learning Suite(TLS)는 처음으로 경험해보는 multi-container application이다. 중간에 native개발을 위한 환경 설정을 끼워 넣어 보려고 이것 저것 시도하는 중에 web interface를 담당하는 tls_proxy라는 docker가 Bad Gateway를 띄우면서 문제가 생겼다. 디버깅 하는 과정에서 사용해 본 docker-compose명령어 들의 쓰임새가 유용할 것 같아서 기록으로 남겨 둔다.

Port number와 docker찾기: docker-compose ps

Web browser로 서비스에 접근하면(https://localhost) Bad Gateway(502) error가 발생하는데 처음으로 web-browser의 접속 요청을 받아서 처리하는 docker container는 docker-compose ps 명령어로 docker image별로 listening하는 port number들을 찾을 수 있다.

TLS의 경우는 tls_proxy가 http요청 처리를 위한 80번과 https를 위한 443번 port를 listening하고 있다.

$ docker-compose ps
    Name                  Command               State     Ports                     
------------------------------------------------------------------
tls_apiui      ./webservices/start.sh           Exit 0                                                                                                                                                     
tls_core       ./start.sh                       Up                                                                                                                                                         
tls_mongo      docker-entrypoint.sh --tls ...   Up       127.0.0.1:27017->27017/tcp                                                                                                                        
tls_openvino   /bin/bash                        Exit 0                                                                                                                                                     
tls_proxy      /docker-entrypoint.sh /bin ...   Up       0.0.0.0:443->443/tcp,:::443->443/tcp, 80/tcp                                                                                                      
tls_rabbitmq   docker-entrypoint.sh /init ...   Up       0.0.0.0:15672->15672/tcp,:::15672->15672/tcp, 15691/tcp, 15692/tcp, 0.0.0.0:1883->1883/tcp,:::1883->1883/tcp, 25672/tcp, 4369/tcp, 5671/tcp, 5672/tcp, 0.0.0.0:8883->8883/tcp,:::8883->8883/tcp
tls_redis      docker-entrypoint.sh sh st ...   Up       127.0.0.1:6379->6379/tcp

Docker의 log 보기: docker logs tls_proxy

tls_proxy가 요청을 처리하지 못한 이유는 docker logs <서버 이름>으로 확인할 수 있다.

$ docker logs tls_proxy
2023/05/31 01:31:06 [error] 12#12: *5 connect() failed (113: Host is unreachable) while connecting to upstream, client: 172.31.0.1, server: , request: "GET / HTTP/1.1", upstream: "http://172.31.0.8:3000/", host: "localhost"
...

사설 네트워크인 172.31.0.x로 docker service들을 구성했는데, 그중 인터페이스를 담당하는 172.31.0.1 docker가 172.31.0.8에 요청을 연결해 주어야 하는데 이 부분에서 오류가 나고 있는 모양이다.

Docker network 확인: docker network ls

현재 docker용으로 구성되어 있는 network을 확인해 보면 다음과 같다.

$ docker network ls
NETWORK ID     NAME                DRIVER    SCOPE
d15b28e7a1d5   bridge              bridge    local
acc99b5947d1   cvat_cvat           bridge    local
4485972f8274   host                host      local
6f21c18ba66f   none                null      local
20d132ccfc2a   tls_default         bridge    local

각 네트워크의 구성을 검사(inspect)해 볼 수 있는데 docker network inspect cvat_cvat명령어로 해당 네트워크를 검사해 보면 다음과 같이 위에서 오류가 발생했던 ip인 172.21.0.8번(tls_openvino)이 등록되지 않은 것을 볼 수 있다.

$ docker network inspect cvat_cvat|grep "Name\|IPv4Address"
        "Name": "cvat_cvat",
                "Name": "cvat_redis",
                "IPv4Address": "172.31.0.4/16",
                "Name": "cvat_opa",
                "IPv4Address": "172.31.0.3/16",
                "Name": "cvat_db",
                "IPv4Address": "172.31.0.5/16",
                "Name": "traefik",
                "IPv4Address": "172.31.0.2/16",
                "Name": "cvat",
                "IPv4Address": "172.31.0.6/16",
                "Name": "cvat_ui",
                "IPv4Address": "172.31.0.7/16",
                "Name": "tls_proxy",
                "IPv4Address": "172.31.0.9/16",

Docker의 log 보기: docker logs tls_openvino

문제가 되고 있는 docker container를 특정했으니 docker logs tls_openvino명령어로 다시 한번 해당 container의 로그를 확인해 본다.

error: A hook (`userconfig`) failed to load!
error: Failed to lift app: Error: Attempted to `require('/home/tls/webservices/apiserver/config/env/production.js')`, but an error occurred:
--
Error: ENOENT: no such file or directory, open '../../thirdparty/security/ca/ca_certificate.pem'
    at Object.openSync (fs.js:498:3)
    at Object.readFileSync (fs.js:394:35)
    at Object.<anonymous> (/home/tls/webservices/apiserver/config/env/production.js:70:15)

../../third party/security/ca/ca_certificate.pem파일을 읽지 못해서 오류가 발생했다고 나오는데, 실제로 container에서는 해당 파일의 위치를 상대 경로로 접근하지 않는다. Call stack에 나와 있는 production.js line 70근처의 내용을 보면 먼저 절대경로인 /run/secrets/*에 접근을 시도할 때 예외가 발생해서 상대 경로로 접근을 시도하고(개발용 코드로 추정) 이 마저도 실패한 것이 로그에 나온 것이다.

apiserver/config/env/production.js

try {
  tls_ca = fs.readFileSync("/run/secrets/ca_tls")
  tls_server_cert = fs.readFileSync("/run/secrets/tlsserver_cert")
  tls_server_key = fs.readFileSync("/run/secrets/tlsserver_key")
} catch (err) {
  tls_ca = fs.readFileSync("../../thirdparty/security/ca/ca_certificate.pem");
  tls_server_cert = fs.readFileSync("../../thirdparty/security/TLS_server_cert.crt");
  tls_server_key = fs.readFileSync("../../thirdparty/security/TLS_server_key.pem");
}

정리하자면, 문제는 server secret을 생성하는 과정에서 발생한 것이 docker image상의 key file path read에서 오류를 발생시키고 tls_openvino container가 제대로 뜨지 못한 문제이다.

해결(?)

문제의 원인은 생각보다 싱거웠는데, docker-compose 명령어를 실행시키는 native용 script에서 다음 줄을 연결하기 위한 \를 빼먹는 사소한(!) Bash 문법 오류가 발생 했었고, 이 오류가 무시된 채로 docker-compose build 명령어가 계속 수행되어 service가 up되는 상황까지 된 것이었다.

-       && pip install -U pip wheel setuptools
+       && pip install -U pip wheel setuptools \
        && pip install \

위의 수정과 함께 오류가 생기면 build를 멈추도록 set -e를 bash script에 추가 하는 것으로 이 문제는 일단락 되었다.

CARLA client를 build해야 하는데 clang-8이 없댄다.

2022년 9월 현재 CARLA simulator의 최신 버전인 0.9.13으로 PythonAPI를 빌드하려고 하면 clang-8 version을 요구하는데 Ubuntu 22.04에서는 clang-8의 repository를 추가하고 설치 시도하면 다음과 같이 의존성 오류가 생긴다.

The following packages have unmet dependencies:
 clang-8 : Depends: libllvm8 (>= 1:8~svn298832-1~) but it is not going to be installed
           Depends: libstdc++-5-dev but it is not installable
           Depends: libgcc-5-dev but it is not installable
           Depends: libobjc-5-dev but it is not installable
           Depends: libclang-common-8-dev (= 1:8.0.1+svn369350-1~exp1~20200112113617.82) but it is not going to be installed
           Depends: libclang1-8 (= 1:8.0.1+svn369350-1~exp1~20200112113617.82) but it is not going to be installed
           Recommends: llvm-8-dev but it is not going to be installed
           Recommends: libomp-8-dev but it is not going to be installed
 lld-8 : Depends: libllvm8 (= 1:8.0.1+svn369350-1~exp1~20200112113617.82) but it is not going to be installed
E: Unable to correct problems, you have held broken packages.

그리고 아쉽게도 하위 호환에 문제가 있는 것인지 설치 가능한 최하 버전인 clang-10으로는 CARLA client build가 되지 않는다. 빌드 스크립트에서 버전 점검하는 부분을 건너 뛰도록 수정하고 clang-10으로 강제 빌드를 시도해봤더니 역시나 빌드오류가 나면서 일이 커질것 같다는 느낌이 강하게 든다.

일일이 빌드오류 잡는 삽질을 하고 ‘CARLA client build 삽질기’를 포스팅 할 수도 있었겠지만 이번에는 문명의 이기인 Docker를 한번 누려 보기로했다.

먼저, Docker로 clang-8 설치가 가능한 Ubuntu 18.04에서 빌드를 수행한 다음(참고 CARLA – Linux Build) 빌드가 완료되면 Python client package를 host에 설치한다.

  • Docker로 Ubuntu18.04를 설치하고 CARLA의 소스 코드위치를 /carla로 마운트하여 실행한다.
$ docker pull ubuntu:18.04
$ docker run -ti -v <carla-root-path>:/carla ubuntu:18.04 /bin/bash
  • Clang-8의 repository를 추가하고 관련된 패키지들을 설치한다.
# Clang-8 repository 추가.
DOCKER# apt-get update &&
apt-get install wget software-properties-common &&
add-apt-repository ppa:ubuntu-toolchain-r/test &&
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|apt-key add - &&
apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-8 main" &&
apt-get update

# Install packages.
DOCKER# apt-get install -y build-essential clang-8 lld-8 g++-7 cmake ninja-build libvulkan1 python python-pip python-dev python3-dev python3-pip libpng-dev libtiff5-dev libjpeg-dev tzdata sed curl unzip autoconf libtool rsync libxml2-dev git
  • Clang-8을 default로 설정한다.
# Set clang-8 as a default clang.
DOCKER# update-alternatives --install /usr/bin/clang++ clang++ /usr/lib/llvm-8/bin/clang++ 180 && update-alternatives --install /usr/bin/clang clang /usr/lib/llvm-8/bin/clang 180
  • Host machine의 python과 동일한 버전(3.8)을 docker에도 설치하고 관련된 Python package들을 pip로 설치해준다. 특별히 setuptools package는 버전의 영향을 받으므로 확인된 버전(47.3.1)을 명시해 준다.
DOCKER# apt install -y python3.8 python3.8-dev

# PIP upgrade
DOCKER# pip3 install --upgrade pip && pip install --upgrade pip

# 중요. Python3의 setuptools version이 안맞으면 빌드에 실패할 수 있으니 버전명을 명시해 준다.
DOCKER# pip2 install setuptools &&
pip3 install -Iv setuptools==47.3.1 &&
pip2 install distro &&
pip3 install distro &&
pip2 install wheel &&
pip3 install wheel auditwheel
  • 이제 해당 버전으로 docker에서 build를 시도 한다.
DOCKER# cd /carla
DOCKER# make PythonAPI ARGS="--python-version=3.8"
  • /carla/PythonAPI/carla/dist 아래에 소스에 포함되어 있는 3.6용 package외에 새롭게 빌드한 3.8용 *.whl, *.egg file들이 생성된 것을 확인하고 host system에 설치해 준다.
DOCKER# ls /carla/PythonAPI/carla/dist/
carla-0.9.13-cp36-cp36m-linux_x86_64.whl  carla-0.9.13-py3.6-linux-x86_64.egg
carla-0.9.13-cp38-cp38m-linux_x86_64.whl  carla-0.9.13-py3.8-linux-x86_64.egg

# CARLA client 설치
$ sudo apt install -y <carla-root-path>/PythonAPI/carla/dist/carla-0.9.13-py3.8-linux-x86_64.egg