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 같은 다른 메일 서비스들을 고려해 보는게 좋겠다.

VScode test explorer에 Rust 테스트 케이스 표시

rust-analyzer extension을 설치했는데도 test explorer에서 rust unit test가 표시되지 않는 경우는 다음을 확인해 보자.

Command palette을 열어서 open settings를 검색한다.

왼쪽 pane에서 Extensions -> rust-analyzer를 선택하고 Test Explorer 항목을 찾아 "Show the Test Explorer view."가 체크되어 있는지 확인한다.

한참을 찾았는데 결국은 이게 disabled된 것이 원인 이었다.

이 항목을 체크하고 나면 vscode를 재실행 하겠다는 창이 뜨고, 재실행 뒤에는 unittest가 text explorer에 표시된다.

참고로 UI를 통해서가 아닌 JSON 파일을 직접 수정하려면 settings.json을 열어서 다음을 추가해 주어도 된다.

{
...
    "rust-analyzer.testExplorer": true,
}

MAPA: Make All Health-checks Pass Again

핸드폰에 떠 있는 알람 뱃지 조차도 모두 확인해야 직성이 풀리는 성격의 소유자에게 WordPress의 건강화면에서 항상 보이는 저 두개의 문제점들은 여간 눈에 거슬리는게 아니다. 특별히 기능에 별 문제가 없음에도 무언가 해야 할 것이 남은 듯한 찜찜함에 또 삽을 들었다.

건강문제 1: “지속적인 객체 캐시를 사용해야 합니다”

객체 캐시 서비스를 설정하지 않아서 보고되는 내용으로, Redis나 Memcached 같은 캐시 서비스를 설치해서 해결 할 수 있다. 다음의 명령어로 AL2023에서 Redis6를 설치해 준다.

sudo dnf install redis6 -y
sudo systemctl start redis6
sudo systemctl enable redis6 

그리고 나서 Redis6와 PHP가 소통할 수 있도록 php-redis도 설치해 준다.

sudo dnf install php-redis -y
sudo systemctl restart php-fpm

Redis6의 설정파일인 /etc/redis6/redis6.conf에 다음 두 줄을 추가해서 메모리 사용량은 128MB로 제한한다. 지금 사용하고 있는 plan의 메모리가 그다지 여유롭지는 않기 때문에 이정도 크기로 제한을 두었다.

maxmemory 128mb
maxmemory-policy allkeys-lru

마지막으로 WordPress에서 Redis Object Cache plugin을 설치하고 활성화 시켜준다.

건강문제 2: “패이지 캐시가 감지되지 않았으나 서버 반응시간이 좋습니다”

매번 페이지가 로드될 때마다 PHP process를 거치지 않아도 되도록 static cache를 설정해 달라는 내용이다. 아주 간단하게는 WordPress plugin을 설치해서 해결할 수도 있는데, 문제는 이러한 plugin들이 대부분 고유주소(permalink) 형식을 다른 것으로 변경하는 것을 요구한다는 것이다.

검색엔진의 상위에 뜨기 위해서라도 이 설정을 "글이름” 형식으로 설정하는게 좋다고는 하는데 검색엔진 상위에 뜨는 건 딱히 관심사도 아닌데다가 무엇보다도 저 형식은 별로 예쁘지 않다.

다행히도 Nginx에서 FastCGI caching을 설정하는 방법으로 static caching을 달성할 수 있다. 먼저 캐시로 사용할 공간을 만들어 준다.

sudo mkdir -p /var/run/nginx-cache
sudo chown nginx:nginx /var/run/nginx-cache
sudo chmod 700 /var/run/nginx-cache

Nginx의 전역 설정파일인 /etc/nginx/nginx.confhttp 영역에 캐시의 경로와 메모리 사용량(10MB)을 정의한다.

fastcgi_cache_path /var/run/nginx-cache levels=1:2 keys_zone=wpcache:10m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";

다음으로 server 영역에 관리자 페이지나 포스트 작성 처럼 캐시를 사용하지 않을 경우를 설정한다.

location ~ \.php$ {
...
    # Cache예외 경우 설정
    set $skip_cache 0;
    if ($query_string != "") { set $skip_cache 1; }
    if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") {
        set $skip_cache 1;
    }
    if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
        set $skip_cache 1;
    }
    
...
}

그리고 캐시 사용을 다음과 같이 설정한다. 아래의 "디버깅 목적"에 있는 헤더에 내용을 추가하는 부분은 굳이 넣지 않아도 되지만, WordPress의 건강검사 메뉴에서 캐시적용 여부를 판단하는 부분을 위해서 넣어 주었다. 이 부분을 추가해 주지 않으면 정적 캐시가 동작하고 있어도 캐시 관련 메세지가 계속 뜨게 된다.

location ~ \.php$ {
...
    # Cache
    fastcgi_cache wpcache;
    fastcgi_cache_valid 200 301 302 60m;
    fastcgi_cache_bypass $skip_cache;
    fastcgi_no_cache $skip_cache;
        
    # 캐시 적중 여부를 헤더에 표시 (디버깅 목적)
    add_header X-FastCGI-Cache $upstream_cache_status;
    add_header X-Cache-Enabled "True";  # 건강검사 통과용
    add_header X-Proxy-Cache $upstream_cache_status;
...
}

결과

잘했다!