Skip to content

About

Docker repository with instructions how to setup standalone app with nodejs, cobrowsing, php-resque, cronjobs services.

Topics

Resources

Stars

28 stars

Watchers

2 watching

Forks

Repository files navigation

Instructions

This is dockerized version of Live Helper Chat. It includes these images

  • web - nginx service
  • php - php-fpm service
  • cobrowse - cobrowsing running NodeJS service
  • php-cronjob - cronjobs running service
  • php-resque - php-resque worker running service
  • nodejshelper - NodeJS Helper NodeJS running service
  • redis - Redis service
  • db - Database service

Docker instructions

  • Checkout the repository
  • Run cd docker-standalone
  • Copy .env.default to .env
  • Edit .env file and change LHC_SECRET_HASH to any random string
  • Database default settings if you don't change those in .env file.
    • Host - db
    • Database name - lhc
    • Database username - lhc
    • Database password - lhc
  • For version with NodeJS plugin (Recommended)
    • Run install-nodejs.sh this will checkout Live Helper Chat and required extensions
    • Optional. Run docker compose -f docker-compose-nodejs.yml build --no-cache to build from scratch. You might need it you are running on linux/arm64 as I only provide linux/amd64 architecture
    • Run docker compose -f docker-compose-nodejs.yml pull && docker-compose -f docker-compose-nodejs.yml up to use already existingimages
  • For standard version without NodeJS plugin run
    • Run install.sh this will checkout Live Helper Chat and required extensions
    • Optional. Run docker compose -f docker-compose-standard.yml build --no-cache to build from scratch. Build is optional. You might need it you are running on linux/arm64 as I only provide linux/amd64 architecture
    • Run docker compose -f docker-compose-standard.yml pull && docker-compose -f docker-compose-standard.yml up

PHP based images (php, php-cronjob, php-resque)

All three images are built from the same Dockerfiles/php/Dockerfile using different build targets - runtime (php-fpm), cronjob and resque (both add cron on top of the exact same PHP build). That way a cron/resque container can never end up on a different PHP build than the web container, which is how sqlsrv/pdo_sqlsrv used to go missing from them.

docker compose -f docker-compose-nodejs.yml build php php-cronjob php-resque

The runtime target verifies at build time that sqlsrv, pdo_sqlsrv, imagick, redis and imap are loaded, so a broken extension build fails the image instead of a cron job later.

If you build the images yourself, skip docker compose pull - it would replace the images you just built with the published ones from Docker Hub.

Editing cronjobs without rebuilding

The crontabs are bind-mounted from the host, so they can be changed at any time - the image only carries a fallback copy:

host path container path service
Dockerfiles/php/cron/cronjob/ /etc/cron.d php-cronjob
Dockerfiles/php/cron/resque/ /etc/cron.d php-resque

Each directory holds a single file called lhc (the name cron expects; Debian cron ignores files with a dot in the name). Edit it and either wait - cron re-reads a changed crontab within a minute - or restart the container:

docker compose -f docker-compose-standard.yml up -d --force-recreate php-cronjob

Notes:

  • Mount the directory, not the single file. A single-file bind mount pins the inode, so an editor that saves by replacing the file leaves the container running the old crontab.
  • Keep the file root:root and mode 0644 - Debian cron silently ignores crontabs that are group/world writable.
  • Only lhc may live in those directories: everything in /etc/cron.d is executed, so a resque entry must not end up in the php-cronjob directory.

Redis persistence (PHP sessions)

PHP sessions are stored in Redis - conf/php-fpm/zz-lhc.conf sets session.save_handler = redis and session.save_path = tcp://redis:6379. To keep them across restarts and container recreation, the redis service bind mounts ./redisdata at /data (same idea as ./dbdata for MySQL) and runs with --appendonly yes. Without it, a docker compose up -d recreate wipes every logged-in visitor.

  • docker compose down keeps the data; ./cleanup.sh deletes it (all sessions are dropped and everybody has to log in again).

  • The fsync policy comes from REDIS_APPEND_FSYNC in .env (see .env.default), default everysec. Use always for maximum durability, no for maximum throughput - the file itself is always written, this only controls when it is flushed to disk.

  • Verify with docker compose exec redis redis-cli info persistence (aof_enabled:1, aof_last_write_status:ok) or docker compose exec redis redis-cli dbsize.

  • The data is a plain host folder, so it can be backed up/copied like the DB: tar czf redisdata.tgz redisdata while the container is stopped, or just copy redisdata/appendonlydir/.

  • docker compose down -v does not remove it (bind mounts are never deleted by Compose); use ./cleanup.sh or rm -rf redisdata.

  • Upgrading an existing install: the old data sits in the previous container's writable layer. Copying only dump.rdb is not enough - with --appendonly yes and no AOF present Redis starts with an empty dataset and ignores the RDB. Convert the live instance first, then copy appendonlydir:

    # 1. let the running Redis rewrite its current dataset into the AOF
    docker exec docker-standalone-redis-1 redis-cli config set appendonly yes
    until [ "$(docker exec docker-standalone-redis-1 redis-cli info persistence \
      | awk -F: '/^aof_rewrite_in_progress/{gsub(/\r/,"",$2);print $2}')" = 0 ]; do sleep 1; done
    # 2. copy the data directory to the host folder
    mkdir -p redisdata
    docker cp docker-standalone-redis-1:/data/. ./redisdata/
    # 3. recreate the service with the bind mount
    docker compose -f docker-compose-standard.yml up -d redis
    docker exec docker-standalone-redis-1 redis-cli dbsize   # same count as before

"Bad file format reading the append only file" (crash loop)

AOF is a log of write commands. If the process dies in the middle of appending one, the file ends with a half-written command and Redis refuses to start:

# Bad file format reading the append only file appendonlydir/appendonly.aof.4.incr.aof at offset 55526797
redis-1 exited with code 1 (restarting)

The write itself is fine - the usual causes are an unclean stop: docker kill/rm -f, sigkill after the 10s stop timeout, container/host OOM-kill, power loss, or a full disk (the write returns ENOSPC half way). It is not caused by normal session churn.

What this repo already does to avoid it:

  • stop_grace_period: 1m on redis - docker stop gives Redis a minute to fsync and exit instead of SIGKILLing it after the compose default of 10s.
  • --aof-load-corrupt-tail-max-size (REDIS_AOF_CORRUPT_TAIL_MAX, default 134217728 = 128 MiB, bytes - the option has no mb suffix) - a torn tail is truncated at startup and Redis comes up normally, losing only the last few commands, instead of crash-looping until an operator runs redis-check-aof.

Still up to you on the host:

  • Never docker kill, docker rm -f or systemctl kill the redis container; use docker stop / docker compose stop redis.
  • Keep free space: Redis writes appendonly.aof.*.incr.aof continuously and needs ~2x the dataset during a rewrite. Watch df and redis-cli info persistence | grep aof_current_size; once the disk is full Redis logs Can't write to the AOF file, disk full? and every later restart can be corrupt. Keep >= 10-15% free.
  • Do not starve it of RAM: an OOM kill is an unclean stop. Check dmesg -T | grep -i 'killed process'. Cap the dataset with maxmemory if the box is shared (sessions expire on their own, volatile-* policies only evict keys with a TTL).
  • Session-only data does not need appendfsync always; everysec (default) is the right trade-off and also the safest for disk wear.

If it already happened (recovery, loses only the torn tail):

docker compose -f docker-compose-standard.yml stop redis
tar czf redisdata-$(date +%F-%H%M).tgz redisdata            # never fix without a backup
printf 'y\n' | docker run --rm -i -v "$PWD/redisdata":/data redis:alpine \
  redis-check-aof --fix /data/appendonlydir/appendonly.aof.manifest
docker compose -f docker-compose-standard.yml up -d redis
docker exec docker-standalone-redis-1 redis-cli dbsize       # sanity check

--fix truncates the file at the first bad command; everything before it is kept. If the corruption is in the middle of the file (disk error) rather than the tail, the fix still works but you lose the commands after that point - restore ./redisdata from backup instead if that matters. dump.rdb next to appendonlydir/ is a second, independent copy written by the normal snapshot rules.

  • You will need to install composer dependencies:
    1. Open a shell in the docker container
      docker exec -it --workdir /code docker-standalone-php-1 /bin/bash
    2. Visit https://getcomposer.org/download/ and run the commands to get composer.phar
    3. Run this to actually install the dependencies:
      php composer.phar update
      php composer.phar install
  • Edit docker-standalone/lhc-php-resque/lhcphpresque/settings/settings.ini.php and put proper site_address domain value. php-resque does not know what domain it's running
  • Make sure the cache, settings and var folders have the correct permissions:
docker exec -it docker-standalone-web-1 chown -R www-data:www-data /code/cache
docker exec -it docker-standalone-web-1 chown -R www-data:www-data /code/settings
docker exec -it docker-standalone-web-1 chown -R www-data:www-data /code/var
  • Navigate to localhost:8081 and follow install instructions.
    • db username and password: Check .env file
    • db host: db.
    • db port: 3306
  • If you want to run docker as a service append -d to docker commands. docker-compose -f docker-compose-nodejs.yml up -d

How to listen on standard 80 port?

Edit .env file and set LHC_PUBLIC_PORT and LHC_NODE_JS_PORT port to 80

How to setup HTTPS?

That's up to you. You can have in host machine runing nginx and just proxy request or tweak images/docker files I provided. You should play around with web service.

E.g to the ones who are too lazy too use google :D

Tip commands

Generate SSL certificate under docker folder for demo.livehelperchat.com domain. Change paths in this command. You have to be pointed domain to your sever

certbot certonly --config-dir /opt/lhc/docker-standalone/conf/nginx/ssl --webroot --webroot-path /opt/lhc/docker-standalone/livehelperchat/lhc_web -d demo.livehelperchat.com

This is how cronjob can look like. Make sure you put your domain and correct docker-standalone-web-1 name.

0 0 * * * certbot certonly --keep-until-expiring --config-dir /opt/lhc/docker-standalone/conf/nginx/ssl --webroot --webroot-path /opt/lhc/docker-standalone/livehelperchat/lhc_web -d demo.livehelperchat.com --post-hook "docker restart docker-standalone-web-1" > /dev/null 2>&1

If you are migrating server and want to setup fresh certbot, you can just use this command

certbot certonly --manual --preferred-challenges http --config-dir /opt/lhc/docker-standalone/conf/nginx/ssl -d demo.livehelperchat.com

web service part will have to look like

  web:
    image: nginx:latest
    env_file: .env
    ports:
      - "${LHC_PUBLIC_PORT}:80"
      - "443:443"
    volumes:
      - ./livehelperchat/lhc_web:/code
      - ./conf/nginx/site-ssl.conf:/etc/nginx/conf.d/default.conf
      - ./conf/nginx/mime.types:/etc/nginx/mime.types
      - ./conf/nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./conf/nginx/ssl/live/demo.livehelperchat.com/fullchain.pem:/etc/nginx/ssl/demo.livehelperchat.com/fullchain.pem
      - ./conf/nginx/ssl/live/demo.livehelperchat.com/privkey.pem:/etc/nginx/ssl/demo.livehelperchat.com/privkey.pem
    depends_on:
      - db
      - php
      - php-cronjob
    networks:
      - code-network
    restart: always

Modify /conf/nginx/site-ssl.conf and change where you see demo.livehelperchat.com to your domain.

Re-create docker image after a changes.

cd /opt/lhc/docker-standalone && docker compose -f docker-compose-nodejs.yml up -d --no-deps --force-recreate web

Remember to automated SSL issuing workflow :)

cd /opt/lhc/docker-standalone && docker compose -f docker-compose-nodejs.yml down
cd /opt/lhc/docker-standalone && docker compose -f docker-compose-nodejs.yml up -d

How to monitor server status?

Nginx and PHP-FPM status endpoints are available from within the Docker network (e.g. from inside any container) or from the host machine.

The allowed IPs are configured directly in the nginx conf files (conf/nginx/site-nodejs.conf, conf/nginx/site.conf, conf/nginx/site-ssl.conf) under the nginx_status and fpm-status location blocks. By default private network ranges and the Docker host IP are allowed.

To get overview of docker containers

 docker stats

Also check server files limits https://gemini.google.com/share/9f106a446951

Open a shell in the nginx container. Get yours name first docker ps:

docker exec -it docker-standalone-web-1 /bin/bash

Then run any of the following:

# PHP-FPM status (pool summary: accepted conn, active/idle processes, req/sec since start)
curl http://web/fpm-status

# PHP-FPM status with per-worker details (pid, state, last URI, cpu, memory)
curl 'http://web/fpm-status?full'

# PHP-FPM status as JSON (useful for scripts and monitoring tools)
curl 'http://web/fpm-status?json'

# PHP-FPM status in Prometheus metrics format
curl 'http://web/fpm-status?prometheus'

# Nginx connection/request counts
curl http://web/nginx_status

To calculate real-time requests per second for php-fpm, poll accepted conn twice and divide by the interval:

A=0; while true; do \
  B=$(curl -s 'http://web/fpm-status?json' | grep -o '"accepted conn":[0-9]*' | grep -o '[0-9]*'); \
  [ "$A" -gt 0 ] && awk "BEGIN {printf \"req/s: %.2f\n\", $B - $A}"; \
  A=$B; sleep 1; \
done

For nginx calculate real-time requests per second:

A=0; while true; do \
  B=$(curl -s 'http://web/nginx_status' | awk '/accepts handled requests/{getline; print $3}'); \
  [ "$A" -gt 0 ] && awk "BEGIN {printf \"req/s: %.2f\n\", $B - $A}"; \
  A=$B; sleep 1; \
done

To allow additional IPs, edit the allow lines in the relevant nginx conf file and recreate the web container:

docker compose -f docker-compose-nodejs.yml up -d --no-deps --force-recreate web

PHP-FPM error logs

PHP errors are logged to stderr, which Docker captures automatically. The php service is configured with log rotation via Docker's json-file driver (max-size: 50m, max-file: 5), capping total log storage at ~250MB.

To stream PHP error logs in real time:

docker compose -f docker-compose-nodejs.yml logs php

To stream nginx errors in real time

docker compose -f docker-compose-nodejs.yml logs web

To find the raw log file path on the host:

docker inspect --format='{{.LogPath}}' $(docker compose -f docker-compose-nodejs.yml ps -q php)

The file is located under /var/lib/docker/containers/<container-id>/<container-id>-json.log and rotated files are named ...-json.log.1, ...-json.log.2, etc. Root access is required to read them directly.

To change log rotation limits, edit the logging block in docker-compose-standard.yml (or docker-compose-nodejs.yml) under the php service:

logging:
    driver: json-file
    options:
        max-size: "50m"
        max-file: "5"

My mails are not sending?

You have to edit back office mail settings and use SMTP.

How to update?

# Update docker images
cd docker-standalone && git pull origin master
# If there is any changes you can build your containers
docker compose -f docker-compose-nodejs.yml build --no-cache

# Or just pull new ones
docker-compose -f docker-compose-nodejs.yml pull

# I would recommend also just restart composer containers
docker compose -f docker-compose-nodejs.yml down
docker compose -f docker-compose-nodejs.yml up
# OR start docker as service
docker compose -f docker-compose-nodejs.yml up -d

# All these commands are executed from docker-standalone folder
cd livehelperchat && git pull origin master
cd lhc-php-resque && git pull origin master
cd NodeJS-Helper && git pull origin master

# Login to php-fpm container and execute
docker exec -it docker-standalone-php-1 /bin/bash
# Execute in container. You might need to download composer.phar file
cd /code && php composer.phar update
cd /code && php composer.phar install
cd /code && php cron.php -s site_admin -c cron/util/update_database -p local
cd /code && php cron.php -s site_admin -c cron/util/clear_cache

After install todo

  • Go to Settings -> Live help confgiuration -> Chat configuration -> Screen sharing and
    • Check NodeJs support enabled
    • In socket.io path, optional enter /wsnodejs/socket.io
  • Go to Settings -> Live help confgiuration -> Chat configuration -> Online tracking and
    • Check Cleanup should be done only using cronjob.
    • Check Footprint updates should be processed in the background. Make sure you are running workflow background cronjob.
  • Go to Settings -> Live help confgiuration -> Chat configuration -> Misc and
    • Check Disable live auto assign
  • Go to Settings -> Live help confgiuration -> Chat configuration -> Workflow and
    • Check Should cronjob run departments transfer workflow, even if user leaves a chat
  • Set 'site_address' => 'https://chat.example.com' in settings/settings.ini.php to your install installation path. It has to be location where index.php is located.
  • Set site_address in docker-standalone/lhc-php-resque/lhcphpresque/settings/settings.ini.php
  • After install make sure settings/settings.ini.php file looks like this if you are using php-resque and nodejs
  • array (
    0 => 'lhcphpresque',
    1 => 'nodejshelper',
    ),```
    
    

How to dump database?

As there is no mysql or mysqldump inside of containers docker mysql can be reached using 127.0.0.1:3307 so you can use master host where all your containers are running and just dump database.

How to run everything under proxy with HTTPS support?

server {
    listen         *:80; # This line should be gone once SSL is setup
    server_name    server_name chat.example.com; # Change to your domain

    location / {
	      proxy_set_header X-Real-IP $remote_addr;
	      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	      proxy_set_header Host $http_host;
	      proxy_set_header X-NginX-Proxy true;
	      # Uncomment below two lines if you are running under https. So LHC will pickup https correctly
	      # proxy_set_header X-Forwarded-Proto https;
	      # proxy_set_header X-Forwarded-Ssl on;

	      proxy_pass http://localhost:8081/; # Change port to your docker public port
	      proxy_redirect off;
          proxy_http_version 1.1;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection "upgrade";
    }
    
    # Those will appear automaticlaly once you setup SSL using certbot
    # listen 443 ssl; # managed by Certbot
    # ssl_certificate /etc/letsencrypt/live/chat.example.com/fullchain.pem; # managed by Certbot
    # ssl_certificate_key /etc/letsencrypt/live/chat.example.com/privkey.pem; # managed by Certbot
    # include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

About

Docker repository with instructions how to setup standalone app with nodejs, cobrowsing, php-resque, cronjobs services.

Topics

Resources

Stars

28 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages