분류 설치

Docker Compose로 Wordpress, MariaDB, PHP-FPM, Nginx, Redis, Certbot 설치하기

컨텐츠 정보

본문

3232235777_hiXBlwY8_bf7a36b61dc5de80e63a4be9548dcf0f865aec7b.png


1. 들어가며


이 글은 기존에 적었던 Docker Compose를 이용하여 워드프레스 설치하기(링크)와 90% 이상 동일합니다.


하지만 이 글을 적는 이유는 Redis를 추가하고, W3 Total Cache에 최적화된 워드프레스를 만들 수 있기 때문입니다.


이 셋팅은 현재 제가 테스트로 운영하고 있는 최종 셋팅과 같습니다.


100% 공식 이미지만으로도 이정도 커스텀이 가능하다는 것을 보여드리고 싶었습니다.


공식이미지이므로 업데이트도 잘 될 것입니다. Redis 추가되는 부분만 10글자 추가되고 나머지는 100% 공식 이미지와 같습니다.


Build도 살짝 맛보기로 배울 수 있을 것입니다.



이제 시작합니다.



Docker를 이용하면 호스트(서버)에 직접 설치하는 것이 아닌 가상환경 같이 설치할 수 있습니다.


따라서 기존에 웹서버나 DB서비스가 있어도 독립적으로 실행할 수 있습니다.


이번 가이드는 디지털오션에 있는 글을 최신 버전에 호환되는 것을 확인 후 한글로 작성한 것입니다.


https://www.digitalocean.com/community/tutorials/how-to-install-wordpress-with-docker-compose


자세한 내용은 원문에 작성되어 있으며, 이 글에서는 되도록이면 핵심만 알려드리겠습니다.



2. 준비사항


(1) 도메인 및 DNS 레코드 작업 완료하기


도메인이 있어야겠죠?


도메인이 없다면 fressnom에서 무료 홈페이지 주소를 받을 수 있습니다.


https://blog.wsgvet.com/free-homepage-address-freenom


DNS 레코드는 서버IP 주소와 도메인을 연결하는 작업입니다.


https://blog.wsgvet.com/cloudflare-sign-in-and-change-nameserver


위 링크를 참조하세요. 무료 도메인의 경우 와일드카드 SSL 인증서가 필요하다면 Luadns 추천합니다.


일반적인 경우에는 클라우드플레어가 제일 편합니다. 링크에 모두 설명되어 있습니다.


(2) 서버 또는 가상서버호스팅


직접 돌릴 수 있는 서버도 있어야겠죠?


우분투 최신버전인 20.04 LTS로 진행할 것입니다.


구글 클라우드나 오라클 클라우드에서 무료로 제공해줍니다.


설정하기 편한 곳은 구글 클라우드입니다.


오라클 클라우드는 국내에 서버가 있지만 보안 쪽이 워낙 복잡하여 처음에는 구글 클라우드를 추천드립니다.


https://blog.wsgvet.com/sign-in-google-cloud-platform-and-connect-domain-and-hello-world


위와 같이 셋팅하면 됩니다. 


무료 도메인의 경우 와일드카드 인증서가 필요하다면 클라우드플레어보다는 Luadns가 좋습니다.


와일드카드 인증서가 필요없다면 클라우드플레어도 좋습니다.


그리고 방화벽 셋팅이 되어 있으며, SSH를 위한 22번 포트, Nginx를 위한 80, 443 포트는 열려있어야겠죠? 


추가적으로 phpmyadmin을 위하여 8081 포트도 개방해주세요. phpmyadmin이 필요없다면 열지 않아도 됩니다.



3. 도커 설치


이제 도메인, DNS 레코드, 서버가 있다면 도커를 설치하면 됩니다.


https://www.wsgvet.com/bbs/board.php?bo_table=ubuntu&wr_id=96


위 링크의 1번과 2번을 참고하세요.



4. 시작하기


도커를 설치하기 전에 현재 유저에게 docker를 실행할 수 있는 권한을 줄 것입니다.


그렇지 않으면 docker 명령어를 내릴 때 항상 sudo 명령어를 넣어야 되기 때문에 매우 귀찮습니다. 



먼저 docker 그룹을 만듭니다.



sudo groupadd docker


그리고 현재 유저를 docker 그룹에 추가합니다.



sudo usermod -aG docker $USER


SSH를 다시 접속하거나, 로그아웃 후 다시 로그인하면 적용이 됩니다.


밑의 설명 중에 sudo docker 또는 sudo docker-compose 명령어에서 sudo는 무시하셔도 됩니다.



이제 /home/sammy에서 시작하도록 하겠습니다.


꼭 sammy 유저를 사용할 필요가 없습니다. 그냥 예시로 든 것이며, 자신의 환경에 맞게 쓰시면 됩니다.


대신 아래의 명령어들 중에 sammy 대신 다른 것으로 바꾸면 됩니다.


먼저 sammy 유저로 SSH에 접속합니다.



cd /home/sammy



mkdir wordpress-redis && cd wordpress-redis


위 명령어로 워드프레스 레디스 폴더를 생성하고 워드프레스 레디스 폴더로 이동합니다.


실제 파일들은 /home/sammy/wordpress-redis 이하로 들어갈 것입니다.


그리고 필요한 폴더를 미리 만듭니다.



mkdir php


위 명령어로 php 설정파일이 들어갈 php 폴더를 만듭니다.



mkdir nginx-conf


위 명령어로 nginx 설정파일이 들어갈 nginx-conf 폴더를 만듭니다.



mkdir certbot-etc


위 명령어로 Letsecrypt SSL 인증서가 들어갈 폴더를 생성합니다.



mkdir dbdata


위 명령어로 MariaDB의 DB가 들어갈 폴더를 만듭니다.



mkdir dataredis


위 명령어로 Redis의 Data가 들어갈 폴더를 만듭니다.




mkdir wordpress && chmod 777 wordpress


위 명령어로 워드프레스 설치파일이 들어갈 폴더를 만들고 권한을 777로 줍니다. 


W3 Total Cache 같은 플러그인의 경우 워드프레스의 root 폴더 쓰기 권한이 필요합니다. 그에 대비한 작업이라고 보시면 됩니다.



해당 폴더를 미리 만들어서 연결해두면 굳이 멀리가지 않아도 편하게 접근할 수 있는 장점이 있고, 데이터가 유지되는 장점이 있습니다.



5. Nginx 설정파일 만들기



nano nginx-conf/nginx.conf


위 명령어로 nginx 설정파일을 만듭니다. 우선은 certbot으로 SSL 인증서 획득을 위한 설정만 넣을 것입니다.



server {
        listen 80;
        listen [::]:80;


        server_name example.com www.example.com;


        index index.php index.html index.htm;


        root /var/www/html;


        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }


        location / {
                try_files $uri $uri/ /index.php$is_args$args;
        }


        location ~ \.php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass wordpress:9000;
                fastcgi_index index.php;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param PATH_INFO $fastcgi_path_info;
        }


        location ~ /\.ht {
                deny all;
        }


        location = /favicon.ico { 
                log_not_found off; access_log off; 
        }
        location = /robots.txt { 
                log_not_found off; access_log off; allow all; 
        }
        location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
                expires max;
                log_not_found off;
        }
}


위 내용에서 수정할 곳은 



server_name example.com www.example.com;


입니다. 자신의 도메인으로 바꿉니다.


바꿨으면, 컨트롤 + O, 엔터, 컨트롤 + X 로 저장 후 빠져나옵니다.



6. php.ini 설정파일 만들기


php.ini의 기본 설정은 워드프레스와 어울리지 않는 것이 많습니다.


그래서 해당 부분만 넣으면 잘 작동됩니다.



nano php/php.ini


위 명령어로 php 설정파일을 만듭니다. 



short_open_tag = On
memory_limit = 256M
cgi.fix_pathinfo = 0
upload_max_filesize = 100M
post_max_size = 101M
max_execution_time = 360
date.timezone = Asia/Seoul
expose_php = off


upload_max_filesize와 post_max_size는 업로드 용량을 결정하는 것입니다.


더 많은 업로드 용량이 필요하다면 높여도 됩니다. 다만 post_max_size가 upload_max_filesize보다는 커야 합니다.


expose_php = off 는 php 버전을 숨기는 것입니다. 보안에 효과적입니다.


컨트롤 + O, 엔터, 컨트롤 + X 로 저장 후 빠져나옵니다.



7. 환경변수 설정하기



nano .env


위 명령어로 환경변수로 쓸 파일을 만듭니다.


도커에서는 Mysql이나 MariaDB에 직접 접속하지 않고 환경변수만 지정해도 알아서 DB를 만들어줍니다.


정말 편합니다.



MYSQL_ROOT_PASSWORD=your_root_password
MYSQL_USER=your_wordpress_database_user
MYSQL_PASSWORD=your_wordpress_database_password


위와 같이 ROOT 비번, 워드프레스 DB의 유저, 워드프레스 DB의 비번을 설정합니다.


바꿨으면, 컨트롤 + O, 엔터, 컨트롤 + X 로 저장 후 빠져나옵니다.



8. Dockerfile을 수정하여 php-redis 추가하기


https://hub.docker.com/_/wordpress


워드프레스 공식 도커 이미지 중에서 5.4.2-php7.4-fpm-alpine를 이용할 것입니다.


https://github.com/docker-library/wordpress/blob/master/php7.4/fpm-alpine/Dockerfile


원본은 여기에 있습니다.




wget https://raw.githubusercontent.com/docker-library/wordpress/master/php7.4/fpm-alpine/Dockerfile


위 명령어로 도커파일을 다운 받습니다. 공식이미지라 관리가 잘 되고 있습니다.



nano Dockerfile


위 명령어를 내린 후, 34~35번째 줄에 있는



pecl install imagick-3.4.4; \
docker-php-ext-enable imagick; \


위 내용을



pecl install imagick-3.4.4 redis; \
docker-php-ext-enable imagick redis; \


위와 같이 redis를 넣습니다. 추후에 Dockerfile이 업그레이드되면 imagick 버전이 높아질 수 있습니다.


따라서 redis만 추가하고, 다른건 기존에 있는 그대로 놔둡니다.



혹시 redis가 아닌 다른 php extension이 필요하다면


https://github.com/adhocore/docker-phpfpm/blob/7.4/Dockerfile


위 링크를 참조해서 수정하면 됩니다.


수정 후, 컨트롤 + O, 엔터, 컨트롤 + X로 빠져나옵니다.



wget https://raw.githubusercontent.com/docker-library/wordpress/master/php7.4/fpm-alpine/docker-entrypoint.sh


위 명령어로 도커 파일의 마지막 내용에 필요한 파일을 다운 받습니다.



chmod +x docker-entrypoint.sh


위 명령어로 실행가능한 상태로 바꿉니다.


이제 수정한 Dockerfile과 docker-entrypoint.sh를 이용하여 나만의 이미지를 생성할 것입니다.



9. 새로운 이미지 Build 하기



sudo docker build -t wordpress-fpm-alpine-redis:1.0 .


위 명령어로 Dockerfile과 docker-entrypoint.sh이 들어간 이미지가 생성됩니다.


wordpress-fpm-alpine-redis 라는 이름의 이미지가 생성되고, tag는 1.0이 붙습니다.



Build process completed successfully
Installing '/usr/local/lib/php/extensions/no-debug-non-zts-20190902/redis.so'
install ok: channel://pecl.php.net/redis-5.3.1
configuration option "php_ini" is not set to php.ini location
You should add "extension=imagick.so" to php.ini
configuration option "php_ini" is not set to php.ini location
You should add "extension=imagick.so" to php.ini
configuration option "php_ini" is not set to php.ini location
You should add "extension=redis.so" to php.ini
+ docker-php-ext-enable imagick redis
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz
(1/1) Installing .docker-php-ext-enable-deps (20200806.125833)
OK: 357 MiB in 121 packages
WARNING: Ignoring APKINDEX.2c4ac24e.tar.gz: No such file or directory
WARNING: Ignoring APKINDEX.40a3604f.tar.gz: No such file or directory
(1/1) Purging .docker-php-ext-enable-deps (20200806.125833)
OK: 357 MiB in 120 packages
+ awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }'
+ sort -u
+ tr , '\n'
+ scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions
+ runDeps='so:libMagickCore-7.Q16HDRI.so.7
so:libMagickWand-7.Q16HDRI.so.7
so:libc.musl-x86_64.so.1
so:libfreetype.so.6
so:libjpeg.so.8
so:libpng16.so.16
so:libsodium.so.23
so:libz.so.1
so:libzip.so.5'
+ apk add --virtual .wordpress-phpexts-rundeps so:libMagickCore-7.Q16HDRI.so.7 so:libMagickWand-7.Q16HDRI.so.7 so:libc.musl-x86_64.so.1 so:libfreetype.so.6 so:libjpeg.so.8 so:libpng16.so.16 so:libsodium.so.23 so:libz.so.1 so:libzip.so.5
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz
(1/1) Installing .wordpress-phpexts-rundeps (20200806.125834)
OK: 357 MiB in 121 packages
+ apk del .build-deps
(1/30) Purging .build-deps (20200806.125659)
(2/30) Purging autoconf (2.69-r2)
(3/30) Purging m4 (1.4.18-r1)
(4/30) Purging dpkg-dev (1.20.0-r0)
(5/30) Purging perl (5.30.3-r0)
(6/30) Purging dpkg (1.20.0-r0)
(7/30) Purging file (5.38-r0)
(8/30) Purging g++ (9.3.0-r2)
(9/30) Purging gcc (9.3.0-r2)
(10/30) Purging binutils (2.34-r1)
(11/30) Purging libatomic (9.3.0-r2)
(12/30) Purging libgomp (9.3.0-r2)
(13/30) Purging libgphobos (9.3.0-r2)
(14/30) Purging libc-dev (0.7.2-r3)
(15/30) Purging musl-dev (1.1.24-r9)
(16/30) Purging make (4.3-r0)
(17/30) Purging re2c (1.3-r1)
(18/30) Purging freetype-dev (2.10.2-r0)
(19/30) Purging imagemagick-dev (7.0.10.25-r0)
(20/30) Purging imagemagick-c++ (7.0.10.25-r0)
(21/30) Purging libjpeg-turbo-dev (2.0.5-r0)
(22/30) Purging libpng-dev (1.6.37-r1)
(23/30) Purging libzip-dev (1.6.1-r1)
(24/30) Purging zlib-dev (1.2.11-r3)
(25/30) Purging xz-dev (5.2.5-r0)
(26/30) Purging libmagic (5.38-r0)
(27/30) Purging isl (0.18-r0)
(28/30) Purging mpc1 (1.1.0-r1)
(29/30) Purging mpfr4 (4.0.2-r4)
(30/30) Purging brotli-dev (1.0.7-r5)
Executing busybox-1.31.1-r16.trigger
OK: 113 MiB in 91 packages
Removing intermediate container f60fe24e8915
 ---> 6f0fb5120332
Step 4/12 : RUN set -eux;       docker-php-ext-enable opcache;  {               echo 'opcache.memory_consumption=128';          echo 'opcache.interned_strings_buffer=8';        echo 'opcache.max_accelerated_files=4000';               echo 'opcache.revalidate_freq=2';               echo 'opcache.fast_shutdown=1';         } > /usr/local/etc/php/conf.d/opcache-recommended.ini
 ---> Running in 69b7fb1ba519
+ docker-php-ext-enable opcache
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz
(1/2) Installing binutils (2.34-r1)
(2/2) Installing .docker-php-ext-enable-deps (20200806.125836)
Executing busybox-1.31.1-r16.trigger
OK: 123 MiB in 93 packages
(1/2) Purging .docker-php-ext-enable-deps (20200806.125836)
(2/2) Purging binutils (2.34-r1)
Executing busybox-1.31.1-r16.trigger
OK: 113 MiB in 91 packages
+ echo 'opcache.memory_consumption=128'
+ echo 'opcache.interned_strings_buffer=8'
+ echo 'opcache.max_accelerated_files=4000'
+ echo 'opcache.revalidate_freq=2'
+ echo 'opcache.fast_shutdown=1'
Removing intermediate container 69b7fb1ba519
 ---> dceec18af8f2
Step 5/12 : RUN {               echo 'error_reporting = E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_RECOVERABLE_ERROR';               echo 'display_errors = Off';            echo 'display_startup_errors = Off';            echo 'log_errors = On';                 echo 'error_log = /dev/stderr';  echo 'log_errors_max_len = 1024';                echo 'ignore_repeated_errors = On';             echo 'ignore_repeated_source = Off';            echo 'html_errors = Off';       } > /usr/local/etc/php/conf.d/error-logging.ini
 ---> Running in 727437cf88bd
Removing intermediate container 727437cf88bd
 ---> 32dece8f740f
Step 6/12 : ENV WORDPRESS_VERSION 5.4.2
 ---> Running in f70c80108066
Removing intermediate container f70c80108066
 ---> 81fa3c9ad15f
Step 7/12 : ENV WORDPRESS_SHA1 e5631f812232fbd45d3431783d3db2e0d5670d2d
 ---> Running in 73a35d456978
Removing intermediate container 73a35d456978
 ---> 76dab3f1ed2a
Step 8/12 : RUN set -ex;        curl -o wordpress.tar.gz -fSL "https://wordpress.org/wordpress-${WORDPRESS_VERSION}.tar.gz";    echo "$WORDPRESS_SHA1 *wordpress.tar.gz" | sha1sum -c -;  tar -xzf wordpress.tar.gz -C /usr/src/;         rm wordpress.tar.gz;    chown -R www-data:www-data /usr/src/wordpress;  mkdir wp-content;       for dir in /usr/src/wordpress/wp-content/*/; do           dir="$(basename "${dir%/}")";           mkdir "wp-content/$dir";        done;   chown -R www-data:www-data wp-content;  chmod -R 777 wp-content
 ---> Running in 6bd67884de64
+ curl -o wordpress.tar.gz -fSL https://wordpress.org/wordpress-5.4.2.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 11.6M  100 11.6M    0     0  5276k      0  0:00:02  0:00:02 --:--:-- 5274k
+ sha1sum -c -
+ echo 'e5631f812232fbd45d3431783d3db2e0d5670d2d *wordpress.tar.gz'
wordpress.tar.gz: OK
+ tar -xzf wordpress.tar.gz -C /usr/src/
+ rm wordpress.tar.gz
+ chown -R www-data:www-data /usr/src/wordpress
+ mkdir wp-content
+ basename /usr/src/wordpress/wp-content/plugins
+ dir=plugins
+ mkdir wp-content/plugins
+ basename /usr/src/wordpress/wp-content/themes
+ dir=themes
+ mkdir wp-content/themes
+ chown -R www-data:www-data wp-content
+ chmod -R 777 wp-content
Removing intermediate container 6bd67884de64
 ---> b5b70b589033
Step 9/12 : VOLUME /var/www/html
 ---> Running in fc299b832ad0
Removing intermediate container fc299b832ad0
 ---> 7f447789ff4b
Step 10/12 : COPY docker-entrypoint.sh /usr/local/bin/
 ---> c0e3a7baff69
Step 11/12 : ENTRYPOINT [ "docker-entrypoint.sh" ]
 ---> Running in 168c49797c50
Removing intermediate container 168c49797c50
 ---> 4f18b36cc10b
Step 12/12 : CMD ["php-fpm"]
 ---> Running in a4edbba29a4c
Removing intermediate container a4edbba29a4c
 ---> ace38d1595d1
Successfully built ace38d1595d1
Successfully tagged wordpress-fpm-alpine-redis:1.0


위와 같이 열심히 빌드하여 wordpress-fpm-alpine-redis:1.0 이미지를 생성했습니다.


이제 이 이미지를 활용하여 redis를 지원하는 wordpress LEMP Stack을 만들어봅시다.



10. Docker Compose 파일 설정하기



nano docker-compose.yml


위와 같이 도커 컴포즈 파일 생성합니다.



version: '3'


services:


  db:
    image: mariadb:latest
    container_name: db
    restart: unless-stopped
    env_file: .env
    environment:
      - MYSQL_DATABASE=wordpress
    volumes:
      - ./dbdata:/var/lib/mysql
    networks:
      - app-network


  wordpress:
    depends_on:
      - db
    image: wordpress-fpm-alpine-redis:1.0
    container_name: wordpress
    restart: unless-stopped
    env_file: .env
    environment:
      - WORDPRESS_DB_HOST=db:3306
      - WORDPRESS_DB_USER=$MYSQL_USER
      - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD
      - WORDPRESS_DB_NAME=wordpress
    volumes:
      - ./wordpress:/var/www/html
      - ./php/php.ini:/usr/local/etc/php/php.ini
    networks:
      - app-network


  redis:
    container_name: redis
    image: redis:latest
    restart: unless-stopped
    volumes:
      - ./dataredis:/data
    networks:
      - app-network


  webserver:
    depends_on:
      - wordpress
    image: nginx:alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./wordpress:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - ./certbot-etc:/etc/letsencrypt
    networks:
      - app-network


  certbot:
    depends_on:
      - webserver
    image: certbot/certbot
    container_name: certbot
    volumes:
      - ./certbot-etc:/etc/letsencrypt
      - ./wordpress:/var/www/html
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --staging -d example.com -d www.example.com


  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    container_name: phpmyadmin
    ports:
      - "8081:80"
    environment:
      - PMA_HOST=db
    restart: always
    depends_on:
      - db
    networks:
      - app-network


volumes:
  certbot-etc:
  wordpress:
  dbdata:
  nginx-conf:
  dataredis:


networks:
  app-network:
    driver: bridge



설정파일이 정말 많죠? 상세 내용은 디지털오션 원문을 참조하세요.


https://www.digitalocean.com/community/tutorials/how-to-install-wordpress-with-docker-compose#step-3-%E2%80%94-defining-services-with-docker-compose


우선 db는 MariaDB 최신버전을 사용합니다. mariadb:latest




MYSQL_DATABASE=wordpress


위 내용은 워드프레스의 DB 이름을 지정하는 것입니다. 그냥 놔두면 됩니다.



wordpress 이미지는 방금 만든 php-fpm과 연결된 최신 이미지인 wordpress-fpm-alpine-redis를 씁니다.


https://github.com/docker-library/wordpress/tree/master/php7.4/fpm-alpine


원본은 위 링크에 보면 됩니다.




      - WORDPRESS_DB_HOST=db:3306
      - WORDPRESS_DB_USER=$MYSQL_USER
      - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD
      - WORDPRESS_DB_NAME=wordpress


위 내용은 워드프레스가 MariaDB와 3306포트로 소통하고, 워드프레스 DB의 유저, 워드프레스 DB의 비번은 환경변수 값을 읽는다는 뜻입니다. DB 이름은 wordpress 입니다.



./php/php.ini:/usr/local/etc/php/php.ini


그리고 윗 부분이 php 설정 파일을 덮어쓰는 부분입니다.


웹서버는 nginx:alpine 이며 최신버전에 용량이 최적화되어 있습니다.


Certbot은 Letsencrypt를 이용하는 서비스입니다.



command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --staging -d example.com -d www.example.com


위 내용에서 sammy@example.com과 example.com 은 자신의 것으로 바꾸세요!  --staging은 테스트한다는 뜻입니다. 테스트에서 통과하면 본격적으로 만들 것입니다.


phpmyadmin은 db와 연결되어 MariaDB에 접속할 수 있습니다. 필요없다면 지워도 됩니다.


컨트롤 + O, 엔터, 컨트롤 + X 로 저장 후 빠져나옵니다.



11. 실행하기



sudo docker-compose up -d


위 명령어로 열심히 작성한 도커 컴포즈 파일을 기반으로 실행합니다.


열심히 받고 실행할 것입니다.



:~/wordpress$ sudo docker-compose up -d
Creating network "wordpress_app-network" with driver "bridge"
Creating network "wordpress_default" with the default driver
Creating volume "wordpress_certbot-etc" with default driver
Creating volume "wordpress_wordpress" with default driver
Creating volume "wordpress_dbdata" with default driver
Creating volume "wordpress_nginx-conf" with default driver
Creating volume "wordpress_dataredis" with default driver
Pulling db (mariadb:latest)...
latest: Pulling from library/mariadb
3ff22d22a855: Pull complete
e7cb79d19722: Pull complete
323d0d660b6a: Pull complete
b7f616834fd0: Pull complete
78ed0160f03e: Pull complete
a122e9306ac4: Pull complete
673e89352b19: Pull complete
caf1e694359b: Pull complete
04f5e4f6ead3: Pull complete
a41772aadb3d: Pull complete
c3811aa2fa0a: Pull complete
655ad574d3c7: Pull complete
90ae536d75f0: Pull complete
Digest: sha256:812d3a450addcfe416420c72311798f3f3109a11d9677716dc631c429221880c
Status: Downloaded newer image for mariadb:latest
Pulling redis (redis:)...
latest: Pulling from library/redis
bf5952930446: Pull complete
911b8422b695: Pull complete
093b947e0ade: Pull complete
5b1d5f59e382: Pull complete
7a5f59580c0b: Pull complete
f9c63997c980: Pull complete
Digest: sha256:09c33840ec47815dc0351f1eca3befe741d7105b3e95bc8fdb9a7e4985b9e1e5
Status: Downloaded newer image for redis:latest
Pulling webserver (nginx:alpine)...
alpine: Pulling from library/nginx
cbdbe7a5bc2a: Pull complete
85434292d1cb: Pull complete
75fcb1e58684: Pull complete
2a8fe5451faf: Pull complete
42ceeab04dd4: Pull complete
Digest: sha256:ee8c35a6944eb3cc415cd4cbeddef13927895d4ffa50b976886e3abe48b3f35a
Status: Downloaded newer image for nginx:alpine
Pulling certbot (certbot/certbot:)...
latest: Pulling from certbot/certbot
df20fa9351a1: Already exists
36b3adc4ff6f: Pull complete
3e7ef1bb9eba: Pull complete
78538f72d6a9: Pull complete
a8619c06300a: Pull complete
edf9fcbbda68: Pull complete
27f0dbe677a6: Pull complete
9f2b3356a685: Pull complete
2ebd5cec4a5e: Pull complete
221c4a0e8684: Pull complete
24853c1eb9f7: Pull complete
Digest: sha256:7da47ef03c97c0673f16483b433bc93e8efdaa372c32790e14c674fd584add17
Status: Downloaded newer image for certbot/certbot:latest
Pulling phpmyadmin (phpmyadmin/phpmyadmin:)...
latest: Pulling from phpmyadmin/phpmyadmin
6ec8c9369e08: Pull complete
081a822af595: Pull complete
bb5bea655fca: Pull complete
1e5d9e6a44c7: Pull complete
51c80d726a75: Pull complete
41f3ef5189e5: Pull complete
c1a9c1efdc83: Pull complete
348c6ac67813: Pull complete
d16c4c4b2a5f: Pull complete
035ee560bfbc: Pull complete
4c16f7d16e86: Pull complete
560feb679e04: Pull complete
0bc8defe61af: Pull complete
b80e31e8a7c4: Pull complete
f94927b2554c: Pull complete
416dcf230b63: Pull complete
a9d24c9f2a61: Pull complete
4cae08d2f851: Pull complete
Digest: sha256:69eaf4a23598e9986b62bbfde9e8e3ae773f0da53406723e6f027582e0310274
Status: Downloaded newer image for phpmyadmin/phpmyadmin:latest
Creating db    ... done
Creating redis ... done
Creating wordpress  ... done
Creating phpmyadmin ... done
Creating webserver  ... done
Creating certbot    ... done


위와 같이 모두 done이 뜨면 성공입니다.



sudo docker-compose ps


위 명령어로 현재 상태를 볼 수 있습니다.



$ sudo docker-compose ps
   Name                 Command               State                     Ports
-----------------------------------------------------------------------------------------------
certbot      certbot certonly --webroot ...   Exit 0
db           docker-entrypoint.sh mysqld      Up       3306/tcp
phpmyadmin   /docker-entrypoint.sh apac ...   Up       0.0.0.0:8081->80/tcp
redis        docker-entrypoint.sh redis ...   Up       6379/tcp
webserver    /docker-entrypoint.sh ngin ...   Up       0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
wordpress    docker-entrypoint.sh php-fpm     Up       9000/tcp


위와 같이 certbot은 인증서 Test로 생성 후 Exit 하였고, MariaDB는 3306포트, phpmyadmin은 80포트, redis는 6379포트, webserver는 80, 443 포트, php-fpm은 9000 포트에 연결되어 있고, 실제 노출된 포트는 Nginx의 80, 443포트와 Phpmyadmin의 8081포트입니다.



sudo docker-compose logs 서비스Name


위와 같이 서비스Name 대신에 certbot이나 db의 log를 확인할 수 있습니다.


이제 Certbot이 자기 할일을 하고 SSL 인증서 만드는 테스트에 통과했는지 체크합니다.



sudo docker-compose exec webserver ls -la /etc/letsencrypt/live


위 명령어로 webserver 컨테이너의 인증서 폴더를 볼 수 있습니다.


물론 우리는 SSH에서 certbot 폴더를 만들었으므로


/home/sammy/wordpress-redis/certbot-etc/live


위 경로에 가면 다 있습니다 ^^;




total 16
drwx------    3 root     root          4096 Aug  2 07:41 .
drwxrwxr-x    9 1001     1002          4096 Aug  2 07:41 ..
-rw-r--r--    1 root     root           740 Aug  2 07:41 README
drwxr-xr-x    2 root     root          4096 Aug  2 07:41 example.com



위와 같이 example.com 이라는 도메인의 인증서가 잘 생성된 것을 확인할 수 있습니다.


인증서 테스트가 통과했으므로 이제 진짜 인증서를 받아야겠죠?



12. SSL 인증서 받기



nano docker-compose.yml


위 명령어로 도커 컴포즈 파일을 엽니다.



.
.
.
  certbot:
    depends_on:
      - webserver
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - wordpress:/var/www/html
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --force-renewal -d example.com -d www.example.com
.
.
.


certbot 부분에서 command를 수정합니다. 위와 같이 기존 내용에서 --staging을 --force-renewal로 바꾸면 됩니다. 


sammy@example.com 와 example.com 부분은 아까 자신의 환경에 맞게 바꾸었죠?


수정한 뒤, 컨트롤 + O, 엔터, 컨트롤 + X 로 저장 후 빠져나옵니다.



sudo docker-compose up --force-recreate --no-deps certbot


위 명령어로 의존성없이 certbot 컨테이너만 다시 생성합니다.



Recreating certbot ... done
Attaching to certbot
certbot      | Saving debug log to /var/log/letsencrypt/letsencrypt.log
certbot      | Plugins selected: Authenticator webroot, Installer None
certbot      | Renewing an existing certificate
certbot      | Performing the following challenges:
certbot      | http-01 challenge for example.com
certbot      | http-01 challenge for www.example.com
certbot      | Using the webroot path /var/www/html for all unmatched domains.
certbot      | Waiting for verification...
certbot      | Cleaning up challenges
certbot      | IMPORTANT NOTES:
certbot      |  - Congratulations! Your certificate and chain have been saved at:
certbot      |    /etc/letsencrypt/live/example.com/fullchain.pem
certbot      |    Your key file has been saved at:
certbot      |    /etc/letsencrypt/live/example.com/privkey.pem
certbot      |    Your cert will expire on 2020-10-31. To obtain a new or tweaked
certbot      |    version of this certificate in the future, simply run certbot
certbot      |    again. To non-interactively renew *all* of your certificates, run
certbot      |    "certbot renew"
certbot      |  - Your account credentials have been saved in your Certbot
certbot      |    configuration directory at /etc/letsencrypt. You should make a
certbot      |    secure backup of this folder now. This configuration directory will
certbot      |    also contain certificates and private keys obtained by Certbot so
certbot      |    making regular backups of this folder is ideal.
certbot      |  - If you like Certbot, please consider supporting our work by:
certbot      | 
certbot      |    Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
certbot      |    Donating to EFF:                    https://eff.org/donate-le
certbot      | 
certbot exited with code 0


위와 같이 정상적으로 인증서 발급이 된 것을 볼 수 있습니다.



13. Nginx 설정 수정하기


이제 Nginx에서 SSL 인증서를 사용할 수 있게 수정해야 합니다.



sudo docker-compose stop webserver


위 명령어로 웹서버를 정지합니다.



curl -sSLo nginx-conf/options-ssl-nginx.conf https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf


위 명령어로 Nginx의 SSL 설정을 다운 받습니다.



rm nginx-conf/nginx.conf


위 명령어로 기존에 있던 설정을 지웁니다.



nano nginx-conf/nginx.conf


다시 생성합니다.



server {
        listen 80;
        listen [::]:80;


        server_name example.com www.example.com;


        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }


        location / {
                rewrite ^ https://$host$request_uri? permanent;
        }
}


server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name example.com www.example.com;


        index index.php index.html index.htm;


        root /var/www/html;


        server_tokens off;
        client_max_body_size 100M;


        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
        ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
        include /etc/nginx/conf.d/options-ssl-nginx.conf;


        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header Referrer-Policy "no-referrer-when-downgrade" always;
        add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
        # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
        # enable strict transport security only if you understand the implications


        location / {
                try_files $uri $uri/ /index.php$is_args$args;
        }


        location ~ \.php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass wordpress:9000;
                fastcgi_index index.php;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param PATH_INFO $fastcgi_path_info;
        }


        location ~ /\.ht {
                deny all;
        }


        location = /favicon.ico { 
                log_not_found off; access_log off; 
        }
        location = /robots.txt { 
                log_not_found off; access_log off; allow all; 
        }
        location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
                expires max;
                log_not_found off;
        }
}


위 내용에서 example.com 를 자신의 도메인을 모두 바꿉니다. 모두 7개니깐 꼭 확인하세요!


HTTP Strict Transport Security (HSTS) 부분은 # 으로 주석처리해뒀습니다. 한번 적용하면 해당 브라우저에서는 80포트로 접속이 안되기 때문에 정확하게 확인 후 주석을 제거해주세요.


수정한 뒤, 컨트롤 + O, 엔터, 컨트롤 + X 로 저장 후 빠져나옵니다.



sudo docker-compose up -d --force-recreate --no-deps webserver


위 명령어로 webserver 컨테이너를 재생성합니다. W3 Total Cache 처럼 Nginx 재시작이 필요한 경우 위 명령어로 Nginx restart 같은 효과를 볼 수 있습니다.



sudo docker-compose ps


위 명령어를 내리면



   Name                 Command               State                     Ports
-----------------------------------------------------------------------------------------------
certbot      certbot certonly --webroot ...   Exit 0
db           docker-entrypoint.sh mysqld      Up       3306/tcp
phpmyadmin   /docker-entrypoint.sh apac ...   Up       0.0.0.0:8081->80/tcp
webserver    /docker-entrypoint.sh ngin ...   Up       0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
wordpress    docker-entrypoint.sh php-fpm     Up       9000/tcp


위와 같이 정상적으로 실행되고 있는 것을 볼 수 있습니다.



14. 워드프레스 접속하기


https://example.com


이제 위와 같이 자신의 도메인으로 접속하면


3232235777_S1DxYnq6_9be9c226a8fe418b67293e407822c2ef366df090.png


위와 같이 언어를 변경하는 화면이 나옵니다.


한국어를 선택하고 워드프레스 설정을 하면 됩니다. DB 정보는 이미 입력했기 때문에 아이디 비번 이메일 설정하는 화면이 나올 것입니다.


이제 원하는대로 쓰면 됩니다!



15. SSL 인증서 갱신하기


certbot으로 인증서를 생성했지만 유효기간이 3개월 밖에 되지않기 때문에 갱신 작업도 해줘야겠죠?



nano ssl_renew.sh


위 명령어로 SSL 인증서 갱신 BASH파일을 만듭니다.



#!/bin/bash


COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"


cd /home/sammy/wordpress-redis/
$COMPOSE run certbot renew  --no-random-sleep-on-renew --dry-run && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af



위 내용에서 /home/sammy/wordpress-redis/ 를 자신의 경로로 바꿔주세요.


수정한 뒤, 컨트롤 + O, 엔터, 컨트롤 + X 로 저장 후 빠져나옵니다.



chmod +x ssl_renew.sh


위 명령어로 실행가능하게 만듭니다.



sudo crontab -e


위 명령어로 크론작업으로 들어갑니다.



no crontab for root - using an empty one


Select an editor.  To change later, run 'select-editor'.
  1. /bin/nano        <---- easiest
  2. /usr/bin/vim.basic
  3. /usr/bin/vim.tiny
  4. /bin/ed


Choose 1-4 [1]:


혹시 위와 같이 나오면 1번 엔터를 누릅니다.


제일 밑에



* * * * * /home/sammy/wordpress-redis/ssl_renew.sh >> /var/log/cron.log 2>&1


위와 같이 넣습니다. 파일 경로는 자신에게 맞게 수정해야 합니다. 컨트롤 + O, 엔터, 컨트롤 + X 로 저장 후 빠져나옵니다.


이제 매분 ssl 갱신을 시도할 것입니다.


1분 뒤에



tail -f /var/log/cron.log


위 명령어를 내려보세요. 실시간으로 어떻게 작업이 되는지 확인할 수 있습니다.



Starting webserver ...
Starting webserver ... done
Saving debug log to /var/log/letsencrypt/letsencrypt.log


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/example.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert not due for renewal, but simulating renewal for dry run
Plugins selected: Authenticator webroot, Installer None
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for example.com
http-01 challenge for www.example.com
Using the webroot path /var/www/html for all unmatched domains.
Waiting for verification...
Cleaning up challenges


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/example.com/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)


Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Killing webserver ...
Killing webserver ... done


위와 같이 



new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/example.com/fullchain.pem


위 내용이 뜨면 갱신 테스트가 성공했다는 뜻입니다.


컨트롤 + c 를 누르면 빠져나와집니다.



sudo crontab -e


위 명령어로 갱신 명령 주기를 수정합니다.



0 12 * * * /home/sammy/wordpress-redis/ssl_renew.sh >> /var/log/cron.log 2>&1


위와 같이 매일 12시 0분에 갱신 명령을 내리도록 합니다.


/home/sammy/wordpress-redis 경로는 자신의 환경에 맞게 수정하세요!


수정한 뒤, 컨트롤 + O, 엔터, 컨트롤 + X 로 저장 후 빠져나옵니다.





nano ssl_renew.sh


위 명령어로 갱신 명령어도 수정합니다.



#!/bin/bash


COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"



cd /home/sammy/wordpress-redis/
$COMPOSE run certbot renew && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af



위와 같이 --no-random-sleep-on-renew --dry-run 을 뺍니다. /home/sammy/wordpress-redis/는 꼭 자신의 경로로 바꿔주세요.


컨트롤 + O, 엔터, 컨트롤 + X 로 저장 후 빠져나옵니다.





nano docker-compose.yml


다시 도커 설정파일로 들어갑니다.


도커 설정파일에는 도커를 완전 정지 후 다시 실행하면 --force-renewal 옵션에 의해 갱신기간이 오지 않아도 재갱신하는 셋팅이 되어있습니다. 그래서 수정합니다.



  certbot:
    depends_on:
      - webserver
    image: certbot/certbot
    container_name: certbot
    volumes:
      - ./certbot-etc:/etc/letsencrypt
      - ./wordpress:/var/www/html
    command: renew


위와 같이 certbot 부분의 command를 단순히 renew로 변경합니다.


그러면 강제로 갱신하지 않고 갱신기간이 왔을때만 갱신하게 됩니다.


물론 crontab에서 매일 갱신 체크를 하게되니 걱정없지만요 ^^


이제 SSL 갱신까지 끝났습니다.



16. W3 Total Cache와 Redis 연결하기


무료 플러그인 중에서 W3 Total Cache와 Autopimize를 좋아합니다.


W3 Total Cache : https://wordpress.org/plugins/w3-total-cache/


Autoptimize : https://wordpress.org/plugins/autoptimize/


W3 Total Cache의 경우 Minify를 켰을 때 문제가 생기는 경우가 많았습니다.


따라서 해당 옵션은 활성화하지 않고, Autoptimize 플러그인의 Minify를 이용하면 됩니다.


두개 조합해서 사용하면 유료 플러그인의 80~90% 정도의 효율을 낼 수 있다고 생각합니다.


그리고 우리는 이 글을 통해 Redis라는 강력한 기능까지 쓸 수 있습니다.


W3 Total Cache를 설치 후 활성화하면, 첫화면에서 Nginx 설정이 바뀌었으니 웹서버를 재시작하라고 합니다.


해당 내용이 나올때마다



sudo docker-compose up -d --force-recreate --no-deps webserver


위 명령어를 SSH를 통해 넣어주면 됩니다.


W3 Total Cache의 핵심은 Page Cache, Database Cache, Object Cache라고 생각합니다.


무료 기능이지만 속도향상에 가장 큰 도움을 주기 때문입니다.


플러그인 화면에서 General Setting에 들어가서 


3232235777_tWEcLSNB_c9cca4e744431410eebc134f6531a60d7f3d9456.png


3232235777_Wgsz6BHN_db1aec41ccb6e2e6b3fd7f5c991feb1e0ecbe511.png


3232235777_MhS1Vouy_034670084a4d95350410bdf40825c880fa087d28.png


위와 같이 각각 Enable에 체크 후 Method에는 Redis를 선택합니다.


그리고 Save Setting & Purge Caches를 누릅니다.


이제 각각의 세부 설정에 들어갑니다.


Page Cache, Database Cache, Object Cache 각각의 페이지에 보면


Redis hostname:port / IP:port:


위와 같이 Redis의 호스트이름과 포트를 넣으라고 나옵니다.


3232235777_eVRmyqYk_1262e1bf0917039e9ace359280c88116f6b18540.png


redis:6379 를 넣고 Test를 누르면 Test passed. 라고 나올 것입니다.


redis 만 넣어도 되네요 ^^;;


각각의 페이지 모두 바꿔주고 Save Setting & Purge Caches를 누릅니다.


이제 워드프레스의 W3 Total Cache에서 Redis를 쓸 수 있습니다.


캐시가 Redis의 RAM에 저장되기 때문에 읽고 쓰는데 속도가 매우 빠릅니다.



감사합니다.

관련자료

댓글 3개 / 1페이지

우성군님의 댓글의 댓글

아직 내공이 부족합니다. ㅎㅎ

일단 그누보드 도커 만들고 있어요~

공부 더 하면... 만들 수도 있겠죠?

우성군님의 댓글의 댓글

https://github.com/litespeedtech/ols-docker-env

위 링크는 공식 이미지


https://hub.docker.com/r/demyx/openlitespeed

위 링크는 공식이미지보다 인기가 더 많은 사설 이미지네요 ㅎㅎ


Nginx와 오픈라이트스피드를 섞으면 힘드니..

잘 만들어진 오픈라이트스피드 전용 도커 이미지를 쓰는게 정신건강에 좋을 듯 합니다. ㅎㅎㅎ
Total 32 / 1 Page
RSS
Keep Network의 ECDSA & Beacon nodes 구글 클라우드에 설치하기 댓글 2

설치 Keep Network의 test Keep token으로 ECDSA 및 Beacon nodes를 구글 클라우드에서 직접 구축할 수 있습니다.다른…

중국 황금방패 뚫는 VPN 3대장 직접 설치하기 댓글 6

설치 중국에서 해외 사이트에 접속할 때 황금방패로 막혀있는 경우가 많습니다.보통 유료로 VPN에 가입해서 사용하거나, 무료 VPN 서비스를 사용할텐데…

윈도우10에 우분투 설치하는 방법

설치 윈도우10에는 WSL(Windows Subsystem For Linux)이 있어서 우분투 센토스 등을 설치할 수 있습니다.방금 설치해보니 정말 …

Docker compose로 워드프레스 편하게 설치하는 방법 댓글 2

설치 들어가며Wordpress(워드프레스)는 현재 전세계에서 가장 많이 쓰이는 CMS입니다.php, mysql, 웹서버로 구성됩니다.SSL 인증서 설…

그누보드 이미지 및 동영상 캐시 서버 구축 방법 댓글 9

설치 그누보드의 /data 폴더에 있는 이미지 및 동영상을 캐시 서버에서 트래픽을 나눌 수 있는 방법입니다.이미지나 동영상이 많아서 트래픽이 많이…

Docker compose로 그누보드 편하게 설치하는 방법 댓글 9

설치 들어가며그누보드는 이 홈페이지가 돌아가고 있는 국산CMS입니다.현재 5.4버전이 개발 중이며 사용자도 많습니다.그누보드는 php, mysql, …

최근글


새댓글