Skip to content

Install Easy8 with Docker

This file introduces how to use the Docker image and explains its variables. It provides examples of how to run it via docker-compose and instructions on how to build the image.

Quick start

Follow these steps to get Easy8 up and running quickly:

  1. Prerequisites

    • Docker and Docker Compose installed

      We are testing docker in following versions:

      • docker engine ~> Docker version 29.1.5
      • docker compose ~> Docker Compose version v5.0.1
    • Download the Docker installation package from Client Zone and unpack it on the server

    • Adjust the docker-compose.yml from the package to your requirements (ports, volumes, etc.)
    • Minimum 100GB of disk space (consider more for user uploads)
    • Port 3000 available (or configure a different port)
  2. Initial Setup

    # Create necessary directories
    mkdir -p files log
    # Set correct permissions (UID/GID 5000)
    sudo chown -R 5000:5000 files log
    
  3. Configuration

    • Copy env.EXAMPLE to .env
    • Update required variables in .env
    • Configure your firewall to allow access to port 3000
  4. Start the Application

    # Run database migrations
    docker compose run --rm app install
    # Start all services
    docker compose up -d
    
  5. Access the Application

    • Open your browser and navigate to http://your-server:3000
    • Click on "Register" to create your first admin account
    • Complete the registration form with your details
    • After registration, you'll have full admin access to the application

For detailed configuration options and troubleshooting, continue reading below.

Troubleshooting

Common issues

Permission issues

If you encounter permission errors during installation or when running containers:

# Fix permissions for required directories
sudo chown -R 5000:5000 files log

Port access issues

By default, the application runs on port 3000. For production, keep Easy bound to localhost and expose it through an HTTPS reverse proxy:

ports:
  - "127.0.0.1:3000:3000"

If you need to expose Easy directly from the Docker host, update the port binding in docker-compose.yml:

ports:
  - "3000:3000"

Direct exposure is not recommended unless access is protected by firewall and TLS termination.

Configure your firewall to allow access to port 3000:

# For UFW (Ubuntu)
sudo ufw allow 3000/tcp

# For firewalld (CentOS/RHEL)
sudo firewall-cmd --permanent --add-port=3000/tcp
sudo firewall-cmd --reload

Missing directories

If Docker Compose fails to start due to missing directories:

# Create required directories
mkdir -p files log
# Set correct permissions
sudo chown -R 5000:5000 files log

Disk space

  • Monitor disk space usage regularly, especially the files directory
  • User uploads are stored locally in the files directory
  • Consider setting up monitoring for disk space usage
  • Plan for disk space expansion as your usage grows

Logs

To check container logs:

# View all container logs
docker compose logs -f

# View specific service logs
docker compose logs -f app
docker compose logs -f db

Container management

# Start all services
docker compose up -d

# Restart all services
docker compose restart

# Restart specific service
docker compose restart app

# Stop all services
docker compose down

# Remove all data (including volumes)
docker compose down -v

Overview

This README is designed for experienced users only. This section explains which Docker variables are used and what they do.

Services

This section introduces the basic services used in docker-compose.

  • app = ER/EP application server
  • db = Percona 8.4 database server
  • pdf_chrome = Headless Chrome service used for printable PDF rendering
  • redis = Redis 8 server
  • sidekiq = Sidekiq worker

The DB container is used for the database server, which stores client data. It is mandatory for app & sidekiq containers. The Redis container is used for caching and background jobs. It is also mandatory for app & sidekiq containers because they exchange data between them. The pdf_chrome container exposes Chrome DevTools Protocol inside the Compose network. The Rails image does not need Chrome or wkhtmltopdf for printable PDF rendering.

graph LR
  App[app] --> DB[db]
  App --> Redis[redis]
  App --> PdfChrome[pdf_chrome]
  Sidekiq[sidekiq] --> DB[db]
  Sidekiq --> Redis[redis]
  Sidekiq --> PdfChrome

Printable PDF rendering (pdf_chrome)

The application renders printable PDFs by sending HTML to a headless Chrome instance over the Chrome DevTools Protocol (CDP). The pdf_chrome service provides that Chrome instance; the Rails image itself does not need Chrome or wkhtmltopdf.

The service runs with a read-only root filesystem. Chrome only needs writable tmpfs mounts for /tmp and the fontconfig caches, which are already configured in docker-compose.yml.

Fonts (emoji and non-latin scripts)

Text in the generated PDF can only use fonts that are installed inside the pdf_chrome container (plus any web fonts loaded by the template itself). The default image chromedp/headless-shell:stable ships only the DejaVu font family, which covers Latin, Cyrillic and Greek. Anything outside that coverage (emoji, CJK, Arabic, Hebrew, Thai and others) renders as blank squares (tofu).

To fix this, build a derived image that adds the missing fonts and point PDF_CHROME_IMAGE to it. Example that adds colour emoji and broad Unicode coverage via the Noto fonts:

FROM debian:stable-slim AS fonts
RUN apt-get update \
 && apt-get install -y --no-install-recommends \
      fonts-noto-color-emoji \
      fonts-noto-core \
 && rm -rf /var/lib/apt/lists/*

FROM chromedp/headless-shell:stable
COPY --from=fonts /usr/share/fonts/truetype/noto /usr/share/fonts/truetype/noto
COPY 75-noto-color-emoji.conf /etc/fonts/conf.d/75-noto-color-emoji.conf

The fontconfig rule 75-noto-color-emoji.conf is required so that emoji prefer the colour font. Without it, fontconfig falls back to DejaVu for the few emoji codepoints DejaVu contains, and those render monochrome:

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
  <match target="pattern">
    <test name="family"><string>emoji</string></test>
    <edit name="family" mode="prepend" binding="strong"><string>Noto Color Emoji</string></edit>
  </match>
  <alias><family>sans-serif</family><prefer><family>Noto Color Emoji</family></prefer></alias>
  <alias><family>serif</family><prefer><family>Noto Color Emoji</family></prefer></alias>
  <alias><family>monospace</family><prefer><family>Noto Color Emoji</family></prefer></alias>
</fontconfig>

For CJK coverage, additionally install fonts-noto-cjk (adds roughly 90 MB to the image).

To verify the image, render a PDF from a printable template containing emoji (for example 🚀 🎉 ✅ 😀). All of them must appear in colour.

The following section describes the basics of a Docker image:

ENV variables

You can find an example of .env in env.EXAMPLE in the installation package.

⚠️ For boolean values, use 0 for false and 1 for true (true, yes and on are accepted as well, case-insensitive; anything else is false).

Required

  • APP_IMAGE = Docker image name (for example from our registry.easy.cz)
  • MYSQL_ROOT_PASSWORD = @see Percona
  • DB_HOST = IP/hostname of your database server
  • MYSQL_DATABASE = Name of your database
  • MYSQL_USER = Username for your MySQL server @see Percona
  • SECRET_KEY_BASE = Session key for persistence - do not forget to specify it
  • REDIS_HOST = Hostname of Redis server

Optional

  • MYSQL_PASSWORD (if not specified, MYSQL_ROOT_PASSWORD will be used)
  • DB_PORT = Port of MySQL database (default: 3306)
  • REDIS_PORT = Port of Redis server (default: 6379)
  • REDIS_NAMESPACE = If you want to use the gem "redis_namespace", this allows you to specify your own name (default: easy)
  • DUMP_URL = If specified, it downloads a .sql.gz archive and loads it into MySQL (via mysql command). It must be an http(s) URI that returns an .sql.gz archive. We only use this internally.
  • FORCE_HTTP = Fill if you need to listen using http (EP only supports https so this overrides it to use http (use only for testing and development purposes)
  • ACTION_CABLE_ORIGIN = Must match URL of EP for WebSocket (action cable) to work. By default, this protection is off for server-solution
  • EASY_HOST_ORIGIN = Must match URL of EP for additional protection against host header attacks. By default, this protection is off for server-solution
  • RAILS_PORT = Port of Rails server (default: 3000)
  • RECEIVE_EMAIL_INTERVAL = Fetching interval for the helpdesk (default: every 10 minutes)
  • DISABLE_SIDEKIQ_CRON_JOBS = Do not register sidekiq cron jobs on boot (default: 0)
  • DISABLE_CACHE_STORE = Completely disable cache (default: false, by default it uses redis as cache store)
  • EASY_LOG_TO_STDOUT = Extra log to stdout, it's an "easy" alternative to RAILS_LOG_TO_STDOUT (default: 0)
  • TZ = Change the server timezone (default: Europe/Prague)
  • ENABLE_CLEARTEXT_PLUGIN = Enable the MySQL mysql_clear_password client authentication plugin (default: 0). Required when the database user is authenticated by Percona through LDAP simple bind (authentication_ldap_simple), see Percona LDAP authentication. Applies to both the Rails connection (database.yml) and the wait-for-db startup check.

⚠️ With ENABLE_CLEARTEXT_PLUGIN=1 the database password is sent to the server unhashed. Use it only when the app ↔ database connection is encrypted with TLS (the image uses ssl_mode: preferred; enforce require_secure_transport=ON on the server so a non-TLS fallback is impossible) and the Percona ↔ LDAP connection uses LDAPS. Keep it 0 for the standard caching_sha2_password setup.

Runtime concurrency and database pools
  • RAILS_MAX_WORKERS = Puma worker processes (default: 2; 0 runs a single process without cluster mode).
  • RAILS_MAX_THREADS = Request threads per Puma worker (default: 5).
  • SIDEKIQ_WORKERS = Job threads in Sidekiq's default capsule (default: 2), not a process count. Production also runs a separate one-thread capsule.
  • DB_POOL = Maximum ActiveRecord connections per process and database pool. Leave unset for max(10, RAILS_MAX_THREADS, SIDEKIQ_WORKERS + 1), with a default and minimum of 10 in every environment. Set a positive integer to override this calculation, including the automatic minimum.

Use SIDEKIQ_WORKERS consistently. The previously documented SIDEKIQ_CONCURRENCY does not configure Sidekiq and is no longer used for database pool sizing; rename existing environment settings.

The shared minimum also accommodates concurrency specs, which need eight simultaneous worker connections plus the test thread's connection. If overriding DB_POOL, keep it at least 9 when running these specs; parallel test processes each have their own pool.

Each Puma worker and Sidekiq process owns its own pool. Do not multiply DB_POOL by Puma worker count or add web and job concurrency together. Connections open on demand, up to the pool limit. An explicit override must cover the process's concurrent database users, including any additional capsules, Action Cable work, asynchronous queries or custom threads. If using Sidekiq's CLI -c override, also size DB_POOL for that concurrency plus the extra capsule.

Both Compose services load .env. To tune pools separately, set DB_POOL in each service's environment section, which overrides .env for that service. Recreate affected containers after changing environment settings.

Budget database server connections separately: Puma process count × web pool + Sidekiq process count × job pool, across all replicas, plus headroom for consoles, migrations and other connections. With two Puma workers, one Sidekiq process and the default pool of 10, these three pools have a combined capacity of 30; this is not a promise of 30 immediately opened connections. In single-process Puma mode (RAILS_MAX_WORKERS=0), count one web process.

Printable PDF rendering
  • PDF_CHROME_URL = URL of the Chrome DevTools Protocol endpoint used by printable PDF rendering. In Docker Compose use http://pdf_chrome:9222.
  • PDF_RENDER_BASE_URL = Base URL that Chrome can use to reach the Rails app while rendering printable templates. In Docker Compose use http://app:3000.
  • PDF_CHROME_IMAGE = Docker image used by the pdf_chrome service. The default is chromedp/headless-shell:stable. Note that the default image contains only DejaVu fonts, so emoji and non-Latin scripts render as blank squares. See Printable PDF rendering for a Dockerfile example that adds the missing fonts.
Rails session
  • EASY_SESSION_STORE = Session store type (default: cookie_store)
  • EASY_SESSION_SAME_SITE = Session cookie same site (default: lax, possible values: none, lax, strict)

SMTP configuration

SMTP configuration can be set using environment variables:

  • EMAIL_DELIVERY_METHOD (default: smtp for production), but it can be tmp_file for development or /dev/null to disable
  • SMTP_HOST (default: host.docker.internal)
  • SMTP_PORT (default: 25)
  • SMTP_AUTHENTICATION (default: :none)
  • SMTP_USER_NAME (default: ``)
  • SMTP_PASSWORD (default: ``)
  • SMTP_TLS (default: false)
  • SMTP_ENABLE_STARTTLS_AUTO (default: false)
  • SMTP_OPENSSL_VERIFY_MODE
  • SMTP_OPEN_TIMEOUT (default: 10)
  • SMTP_READ_TIMEOUT (default: 10)

@see https://www.redmine.org/projects/redmine/wiki/EmailConfiguration

Rack::Attack configuration

Rack::Attack configuration prevents brute force attacks. It can be configured using environment variables:

  • EASY_RACK_ATTACK_API_READ_LIMIT (default: 150) How many API GET requests can one IP address make per minute
  • EASY_RACK_ATTACK_API_WRITE_LIMIT (default: 50) How many API POST/PUT/DELETE requests can one IP address make per minute
  • EASY_RACK_ATTACK_EXPORTS_LIMIT (default: 10) How many exports can one IP address make in 5 minutes

⚠️ Important: LIMITS must be a number indicating HOW MANY requests are allowed. 0 means no requests are allowed.

  • EASY_RACK_ATTACK_DISABLED (default: false) Disable Rack::Attack completely
  • EASY_RACK_ATTACK_API_READ_DISABLED (default: false) Disable Rack::Attack for API GET requests
  • EASY_RACK_ATTACK_API_WRITE_DISABLED (default: false) Disable Rack::Attack for API POST/PUT/DELETE requests
  • EASY_RACK_ATTACK_EXPORTS_DISABLED (default: false) Disable Rack::Attack for exports
  • EASY_RACK_ATTACK_FAIL2BAN_DISABLED (default: false) Disable Rack::Attack for POST requests. Safe guard for POST throttling

All blocked requests are logged to the log/rack_attack.log file. Response status code is 429 Too Many Requests.

Docker secrets

The image supports Docker secrets using the standard *_FILE environment variable convention.

For each supported secret you can either:

  • set the environment variable directly (e.g. MYSQL_PASSWORD), or
  • set the path to a secret file via the corresponding *_FILE variable (e.g. MYSQL_PASSWORD_FILE=/run/secrets/mysql_password)

At runtime, the application reads the same env variables you already configure (MYSQL_PASSWORD, SECRET_KEY_BASE, SMTP_PASSWORD, …). If the direct variable is not set, it will look for a corresponding *_FILE variable and read the value from that file.

See Docker documentation for creating and using secrets:

  • Docker Compose: https://docs.docker.com/compose/use-secrets/
  • Environment variables in containers: https://docs.docker.com/guides/use-case/run-app-in-containers/#pass-environment-variables-to-containers

Available secrets

These variables can be provided either directly or via *_FILE:

  • MYSQL_USER / MYSQL_USER_FILE
  • MYSQL_PASSWORD / MYSQL_PASSWORD_FILE
  • MYSQL_ROOT_PASSWORD / MYSQL_ROOT_PASSWORD_FILE
  • SECRET_KEY_BASE / SECRET_KEY_BASE_FILE
  • SMTP_USER_NAME / SMTP_USER_NAME_FILE
  • SMTP_PASSWORD / SMTP_PASSWORD_FILE

Container hardening

The app and sidekiq services are hardened by default in docker-compose.yml:

  • read_only: true makes the container root filesystem immutable.
  • cap_drop: [ALL] removes Linux capabilities not needed by the Rails application.
  • security_opt: [no-new-privileges:true] prevents privilege escalation.
  • Only required runtime paths stay writable.

Writable paths:

Path Reason
/tmp Ruby stdlib, MiniMagick, and native tools need OS temp directory
/opt/easy/current/tmp Rails temporary/runtime files
/opt/easy/current/log application logs
/opt/easy/current/files user uploads and attachments

All other paths should remain read-only. If Easy tries to write elsewhere, treat it as a bug or a missing mount.

Container hardening does not replace host and infrastructure security. Production operators should also configure:

  • HTTPS reverse proxy
  • firewall rules
  • regular backups for the database and files
  • resource limits in the orchestrator
  • registry access control
  • image digest verification
  • host-level seccomp/AppArmor/SELinux policy where required

Mount points

The persistent layer in the container is owned by a user with UID=5000 and GID=5000. This ensures that the files and directories mounted from containers are managed by this specific user, providing consistent permissions and ownership across the container environment.

Required writable mount points:

  • /opt/easy/current/files = User uploads and attachments
  • /opt/easy/current/log = Application logs

The application also uses tmpfs mounts for runtime-only temporary data:

  • /tmp
  • /opt/easy/current/tmp

Running

This section shows examples of how ER can be run inside Docker.

Get image

There are two ways to get the image:

  1. Use an existing image from the Easy registry with a "token" key (preferred)
  2. Build it yourself, see section Build image below

Before pulling an image from our registry, you need to run docker login registry.easy.cz!

Easy Docker registry

If you receive information including registry.git.easy.cz and token, follow these steps:

  1. docker login
  2. Use credentials provided by our support / account (docker_login_credentials.md)
  3. Use the provided image in the APP_IMAGE variable (in .env)

Verify container image

Within the installation package, there is a file image_digest.txt which includes the SHA256 digest of the image. After pulling the image, you can verify it using:

docker image inspect <IMAGE_NAME> --format='{{.RepoDigests}}'

The digests must match!

Image rebuild and vulnerability monitoring

Easy base images are rebuilt weekly by CI. Production application images are also built and released weekly.

Container vulnerabilities are monitored by Aikido.dev. Critical findings trigger an on-demand base image rebuild and follow-up production image rebuild.

Run via docker-compose (preferred)

This method contains the whole stack and migrations, so there are no additional steps. After you set up the .env file (see example in env.EXAMPLE), you manage the whole stack with docker compose.

First, you need to create the .env file next to docker-compose.yml. For example:

APP_IMAGE="registry.git.easy.cz/clients/dummy:latest"
# linked service by docker-compose
DB_HOST="db"
MYSQL_ROOT_PASSWORD="DontUSEmeInAPPlication"
MYSQL_DATABASE=dummy
MYSQL_USER=easy
MYSQL_PASSWORD="easy-super-secret-pasword"
SECRET_KEY_BASE="____CHANGE_ME____KEyJUSTforTESTING"
# linked service by docker-compose
REDIS_HOST="redis"
# office365 example
SMTP_HOST=smtp.office365.com
SMTP_PORT=587
SMTP_AUTHENTICATION=:login
SMTP_DOMAIN=your_domain.com
SMTP_ENABLE_STARTTLS_AUTO=true
SMTP_USER_NAME=email@your_domain.com
SMTP_PASSWORD=password

If you are installing Easy for the first time or performing a regular update, you need to run migrations first to prepare the database schema. Installation is always done automatically before starting the whole stack, but running it before starting other services is recommended.

docker compose run --rm app install

Then you can start the whole stack using compose:

docker compose up -d --pull always

This starts all containers in the background and migrates data. After a few minutes, the application server will listen on port 3000.

Other docker compose commands: see https://docs.docker.com/reference/cli/docker/compose/

docker compose pull                     # pull images from registries
docker compose exec app bash            # start shell console
docker compose log -f                   # see all logs
docker compose ps -a                    # see containers state
docker compose restart                  # restart containers
docker compose down -v                  # stop whole stack and REMOVE data !!!

Verify container hardening

Validate the Compose configuration:

APP_IMAGE=easy:latest docker compose config

Check Ruby temporary directory:

docker compose exec app ruby -rtmpdir -e 'puts Dir.tmpdir'

Expected output:

/tmp

Verify read-only root filesystem:

docker compose exec app sh -lc 'touch /opt/easy/current/readonly-test'

Expected result: command fails.

Verify writable runtime paths:

docker compose exec app sh -lc 'touch /tmp/write-test && touch /opt/easy/current/tmp/write-test && touch /opt/easy/current/log/write-test && touch /opt/easy/current/files/write-test'

Expected result: command succeeds.

Basic container

see https://docs.docker.com/engine/reference/run/

In case you run it using raw docker, please continue with the following steps:

  • Before starting for the first time, you need to run database migration:
docker run -v ${PWD}/files:/opt/easy/files -e MYSQL_DATABASE=easy -e MYSQL_USER=root -e MYSQL_PASSWORD=root easy install
  • Then run the application on port 3000
docker run -p 3000:3000 -v ${PWD}/files:/opt/easy/files -e MYSQL_DATABASE=easy -e MYSQL_USER=root -e MYSQL_PASSWORD=root --name easyproject.hostname easy server

PWD - is the path to your current working directory, you need to mount it because during the first commands you will generate assets and you will need them in the second command.

Custom code (modification plugin)

Customer-specific code lives in a modification_<name> plugin. Keep its folder next to docker-compose.yml and mount it into the app and sidekiq containers with a docker-compose.override.yml in the same folder. Docker Compose reads the override file automatically, and an Easy8 update does not replace it.

services:
  app:
    volumes:
      - ./modification_acme:/opt/easy/current/plugins/easyproject/easy_plugins/modification_acme:ro
  sidekiq:
    volumes:
      - ./modification_acme:/opt/easy/current/plugins/easyproject/easy_plugins/modification_acme:ro

Then run the plugin's migrations and restart:

docker compose run --rm app install
docker compose up -d
  • The containers run as user 5000, so that user must be able to read the plugin files.
  • If you start Easy8 with -f options, add -f docker-compose.override.yml.
  • The image contains precompiled frontend assets. A mounted plugin can add Ruby code, views, translations, routes and migrations, but no stylesheets or scripts that Easy8 compiles; those have to be built into the image (see Build image). Branding needs no build, see Branding on Docker.

How to build the plugin itself is described in Where to put your code.

Nginx example

Easy supports only https, which means some web-proxy must be set up to re-route traffic from your HTTPS port to the application server (on port 3000 by default).

A basic example of nginx config:

# HTTP (80) always redirect to HTTPS (443)
server {
  listen 80;
  listen [::]:80;
  server_name _;
  server_name_in_redirect off;

  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl;
  http2 on;
  server_name  _;

  access_log /var/log/nginx/easy.log;
  error_log /var/log/nginx/easy.err;
  try_files $uri @app;

  ssl_certificate /etc/ssl/certs/path_to_yours.crt;
  ssl_certificate_key /etc/ssl/private/path_to_yours.key;
  # Adjust according to desired upload size
  client_max_body_size 100M;
  location @app {
    proxy_pass http://localhost:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
  location /cable {
    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}

Backup

We recommend backing up the database using the mysqldump command. Also, backing up the files folder is recommended - it stores user uploads.

If you want, you can also backup the .env and docker-compose.yml files.

Restore or migrate from a legacy version

This section provides detailed steps to restore a database and files OR migrate from a legacy package of Easy8 into the Dockerized setup.

Note: Migration from a legacy package is basically restoring from a backup of the database and files.

This guide assumes you have a basic understanding of Docker and the services involved in the Easy8 setup. Examples are based on the provided docker-compose.yml and .env file.

1. Preparing for restoration (or migration)

Before starting the restoration, ensure you have access to:

  • A database backup (restore.sql) compatible with Percona 8.4 (it's recommended to use a Percona 8.4 dump).
    • Note: it must be sql or sql.gz (see https://hub.docker.com/_/percona)

  • A backup of application files (files directory) from the legacy version.

Ensure you understand the services and their interactions as explained in the Overview section of this document. Specifically:

  • The db service stores all database data and requires the backup file to be restored during initialisation.
  • The app service handles application files, which must be owned by the right user.

2. Database restoration

To restore the database, use the docker-entrypoint-initdb.d directory in the db container, which executes SQL scripts during container startup.

Configuration steps:

  1. Place your database dump file (restore.sql) in a folder named docker-entrypoint-initdb.d on the host machine.

    mkdir -p ./docker-entrypoint-initdb.d
    mv /path/to/your/restore.sql ./docker-entrypoint-initdb.d/
    
  2. Important: If you are restoring a current running instance from backup, ensure you destroy the existing db container and its associated volumes to start with a clean state:

    docker compose down -v db
    
  3. Start the containers:

    docker compose up -d db
    

    During startup, the database container runs restore.sql. Follow the progress in the logs:

    docker compose logs -f db
    

Important: Wait until the DB container is ready and the restore is finished before continuing with the next steps.

3. Application files restoration

Application files are persisted in volumes mapped to the app container. These must be migrated and owned by the correct user (UID and GID = 5000). See Mount points for more information.

Configuration steps:

  1. Copy your legacy application files into the files folder, defined in the docker-compose.yml file:

    cp -R /path/to/legacy/files/* ./files/
    

    Note: Ensure that the files are copied to the correct directory (./files) on the host machine. By default, it's next to docker-compose.yml.

  2. Ensure the correct ownership and permissions for the files:

    sudo chown -R 5000:5000 ./files
    
  3. Run migrations only:

    docker compose run --rm app install
    

4. Post-migration steps

Just start the application:

docker compose up -d

By default, the app service will handle database migrations automatically during its startup process, so if you're just switching from legacy to dockerized setup, you can skip step 3.

If any extra commands are needed (for example, when migrating from Redmine or performing specific tasks from the standard Easy8 installation guide), use this pattern to run commands within the app container:

docker-compose run --rm app rails 'your command'

Examples of some commands:

  • Convert Wiki Formatting (required when migrating from Redmine):

    docker-compose run --rm app rails easy:convert_wiki_formatting
    
  • Run Database Migration Explicitly:

    docker-compose run --rm app rails easy:setup
    

Percona 8.4 my.cnf example

Here is example of recommended configuration for Percona 8.4, which is used in our docker image and recommended for production use:

[mysqld]
user                    = mysql
port                    = 3306

tmp_table_size          = 128M
max_heap_table_size     = 128M
skip-external-locking

bind-address            = 0.0.0.0

key_buffer_size         = 64M
max_allowed_packet      = 64M
thread_stack            = 192K
thread_cache_size       = 8
max_connections         = 6000

skip_name_resolve       = 1
collation_server        = utf8mb4_unicode_ci
character_set_server    = utf8mb4

innodb_file_per_table   = 1
innodb_buffer_pool_size = 8192M
innodb_buffer_pool_instances = 8
# print innodb deadlocks to error log
innodb_print_all_deadlocks

# flush innodb once per second
innodb_flush_log_at_trx_commit = 2

# writes on diske in 8M blocks without cache
innodb_flush_method     = O_DIRECT
innodb_log_buffer_size  = 8M
innodb_log_file_size    = 1GB

# join performance
join_buffer_size        = 8M
sort_buffer_size        = 8M
table_open_cache        = 400
open_files_limit        = 250000

sql_mode                = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

binlog_expire_logs_seconds = 864000
max_binlog_size         = 100M

[isamchk]
key_buffer              = 16M

Developer documentation

Warning: The following sections are intended for developers only. If you're just deploying Easy8, you can stop reading here.

Build image

The easiest way to build an image is to use the pre-prepared script docker/build.sh.

The build process requires a running Percona server to prepare the schema & compile assets. Trilogy supports caching_sha2_password authentication when using TLS (configured via ssl_mode: preferred in database.yml).

docker run --rm --network host -p "3306:3306" -e MYSQL_PASSWORD=easy -e MYSQL_DATABASE=easy -e MYSQL_ROOT_PASSWORD=root -d registry.git.easy.cz/public-images/percona:8.4

The build itself needs to pass proper arguments for DB connection:

docker buildx build --network host  --allow network.host --build-arg MYSQL_PASSWORD=root -t "${BASE_IMAGE_TAG}" --load  .

What Dockerfile will use...

In the codebase, there is a directory docker which contains this README and installation files:

  • 2-install_app.sh = Creates runtime directories and generates missing or empty application configuration and secret-token files. It does not read or generate database.yml; the later COPY . build step includes the tracked config/database.yml. Configure database credentials and DB_HOST through environment variables or supported secrets, not local edits to that file. Docker deployments must set DB_HOST (for example, db in Compose); the tracked configuration defaults to localhost.
  • build.sh = Script for self-building the image

For more information, see the Dockerfile in the application root.

Start Easy in docker

  1. Place dump.sql.gz into your tmp folder (create it if it doesn't exist) - MySQL will load this dump automatically, so you can skip migrations.
  2. Update the "db" service to define volume ./tmp:/docker-entrypoint-initdb.d
  3. Start the database in the background
docker compose -f docker-compose.yml up db -d

Backend

Open a shell in the container and work with the code manually - have everything under control.

  1. Start the database in the background
docker compose -f docker-compose.yml run --service-ports app bash
  1. Then you can run bundle exec rake easyproject:install or bundle exec rails c or bundle exec rails s -b 0.0.0.0 ...

M1 Mac users

If you are an arm64 platform user, please note that there is no official Percona 8.4 support for ARM64. That's why docker-compose.yml uses the MySQL image instead.

Ubuntu with apparmor

If you use Ubuntu with apparmor and you get this error: Error response from daemon: cannot stop container:. This command will resolve your problem: sudo aa-remove-unknown

Windows users

  • Be sure you have set up git for UNIX line-endings (https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings#global-settings-for-line-endings)
  • WSL2 - https://docs.docker.com/desktop/settings/windows/