Installation

Don’t want to read all this?

Just drop a link to https://xedant.com/agents/crm/install.md or to https://xedant.com/llms.txt into any AI chat (Claude, ChatGPT, etc.) and ask it to generate the config files and commands. It will read the docs, ask you a few questions about your setup, and hand you a ready-to-use configuration. Save time — let the model do the reading for you.

You can also reach me on Telegram — I’m always glad to help. And that’s not just politeness — I genuinely enjoy talking to like-minded people, especially if you love coding as much as I do.

CRM Agent is a CRM on your server in which an AI agent keeps the data while you watch and give commands. It deploys as a single Docker container, and a separate database is optional: by default an embedded one is already inside. Next to it, optionally, sits your Xedant Agent — then you can run the CRM by talking in chat, and without it everything except the chat with the agent works as usual.

What you will need

  • A server or computer with Docker — Windows, Linux or macOS all work, and an inexpensive server is enough for permanent use.
  • One free port for the interface (3990 in the examples below). A separate database is not needed: if you do not set a connection string, the product starts an embedded database by itself.
  • The login and password for the first sign-in. You can set them in advance with the CRM_APP_LOGIN and CRM_APP_PASSWORD variables; if you leave them unset, the administrator is the first person who registers in the interface.
  • A management key for the agent (CRM_APP_HASH) — if you want your AI agent to change the data itself instead of you doing it by hand. The variable holds not the key itself but its fingerprint: you invent the key once and give it to the agent, and the product remembers the fingerprint and recognises the key by it. Until the fingerprint is set, changes are refused, while viewing in the interface keeps working.
  • Optionally, the address and key of your Xedant Agent. Without them everything works except the chat with the AI agent inside the product.

Installing with Docker Compose

Create a compose.yml file with this content. There is one service here: it needs no separate database, so the whole product fits into a single container. There are two persistent folders — one holds the data, the other the descriptions of your scripts.

services:
  crmagent:
    image: xedant/crm-agent:min-latest
    container_name: crmagent
    ports:
      - "3990:80"
    volumes:
      - crmagent-data:/data
      # The scripts yml config tree (apps/crm) — a named volume so API-edited
      # definitions survive container recreation (seeded on first boot).
      - crmagent-config:/project/apps/crm
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - ASPNETCORE_URLS=http://+:80
      - CRM_APP_DATA=/data
      # CRM_APP_BASE_PATH=/crm  # serve from a first-level subfolder (see ../subfolder-hosting.md)
      # The image ships the minimal idle-memory runtime posture (workstation GC,
      # non-concurrent GC, 1 MB GC regions, W^X and diagnostics IPC off, glibc arenas
      # capped — see the app Dockerfile and dev/idle-memory.md). Compose entries here
      # override the image, e.g. put Server GC back on for a busy instance:
      # - DOTNET_gcServer=1
    restart: unless-stopped
    sysctls:
      fs.inotify.max_user_watches: "524288"
      fs.inotify.max_user_instances: "512"

volumes:
  crmagent-data:
  crmagent-config:

Line by line, what this says and why:

  • image — the English build of the product with an English interface and the name “CRM Agent”.
  • ports — the address the interface opens on: 3990 outside, 80 inside the container. The outside number can be any free port.
  • volumes — two persistent folders. /data holds the database and all the data, while /project/apps/crm holds the descriptions of your scripts and their settings. Without these folders everything disappears when the container is recreated, and the script descriptions would have to be created again.
  • CRM_APP_DATA — that same persistent folder inside the container, where the product keeps everything of its own; it is the folder mounted as the volume above.
  • CRM_APP_BASE_PATH — the commented line for hosting in a subfolder of the site: uncomment it if you want to open the CRM at an address like your-site/crm.
  • The comments about memory and the line # - DOTNET_gcServer=1 — a hint for a busy instance: the image already carries the low-consumption settings, and this line can be uncommented if many people work at the same time (more in the “Saving resources while idle” section below).
  • restart — restarting after a server reboot, so the CRM comes back up by itself.
  • sysctls — two Linux kernel settings raising the file-watching limit: without them the product notices changes in its folders worse at scale.

Then start it:

docker compose up -d

The interface appears on port 3990: http://<server-address>:3990.

Running with one command

If you would rather not keep a settings file, the same thing is done with one docker run command:

docker run -d \
  --name crmagent \
  -p 3990:80 \
  -v crmagent-data:/data \
  -v crmagent-config:/project/apps/crm \
  -e ASPNETCORE_ENVIRONMENT=Production \
  -e ASPNETCORE_URLS=http://+:80 \
  -e CRM_APP_DATA=/data \
  --sysctl fs.inotify.max_user_watches=524288 \
  --sysctl fs.inotify.max_user_instances=512 \
  --restart unless-stopped \
  xedant/crm-agent:min-latest

The remaining variables are convenient to pass in a file with the --env-file .env flag — easier to change them without rewriting the long command.

Saving resources while idle

While nobody is working in the CRM, the product spends neither processor time nor extra memory. It has no permanent background checks: it wakes on an event — a request came in, a letter arrived, a scheduled deadline is due. A measurement on a server showed 0.0–0.3 % of the processor at rest; the previous version spent 0.77 % continuously.

Memory is returned to the server as well. If nothing happens in the CRM for a full 15 minutes, the accumulated data is released. The window is set by the CRM_APP_IDLE_TRIM_MINUTES variable: 15 minutes by default, 0 turns it off. You can see what the product is occupying right now at the service address /api/health — it is protected by sign-in, like everything else.

The image already carries the low-consumption memory settings, so nothing needs tuning separately. If the instance is a busy one and you want headroom at peaks, compose.yml has a commented-out line # - DOTNET_gcServer=1: uncomment it.

Environment variables

Settings are passed through environment variables — you write them once into compose.yml and change them without touching the code. Almost all names start with CRM_APP_; for the chat with the agent, the names are the familiar ones — without the prefix.

VariableWhat it sets
CRM_APP_DBdatabase choice. Not set — the product runs on the embedded database in the file /data/data.db; set to a connection string — it runs on PostgreSQL
CRM_APP_DATApersistent data folder: the database, sign-in keys, brand settings. Default /data
CRM_APP_BRANDbuild brand: xedant is the English build (the default), pastukhov the Russian one with the Russian product name and interface
CRM_APP_BASE_PATHhosting in a subfolder of the site, for example /crm; not set — runs at the domain root
CRM_APP_HASHfingerprint of the management key: by it your AI agent changes the data over the API (the management addresses it calls). Stored as an irreversible SHA-256 sum; not set — changes are refused, while viewing keeps working
CRM_APP_LOGIN, CRM_APP_PASSWORDthe administrator account created at startup. If you leave them unset, the administrator is the first person who registers in the interface
CRM_APP_INTAKE_ALLOW_ORIGINthe list of sites intake requests are accepted from. Not set — requests are accepted from any site; set — only from the listed ones
CRM_APP_COMPRESSIONserver response compression. Enabled by default, =0 disables it
CRM_APP_JOB_KEEP_DAYShow many days to keep finished background jobs. Default 30
CRM_APP_IDLE_TRIM_MINUTEShow many minutes of complete idleness to wait before releasing the accumulated data and returning memory to the server. Default 15, 0 disables it
CRM_APP_SEARCH_INDEX_FOLDERthe folder holding the search list for product-wide search. By default next to the data, in the search-index folder
CRM_APP_BOOT_FULL=1 — a full check of the database layout at startup. Useful for diagnostics, normally not needed
CRM_APP_SSL=auto — the product issues and signs an HTTPS certificate itself for access at a direct address with a port. If your proxy with its own certificate stands in front of the product, this variable is not needed
AGENT_API_URL, AGENT_API_KEYaddress and key of your Xedant Agent: with them the chat with the AI agent appears inside the CRM. Not set — the chat is hidden, everything else works

The .env file next to the project

Some settings and all secrets are more convenient to keep not in environment variables but in a .env file at the root of the project folder — inside the container that is /project/.env. Its entries are simple, one per line: CRM_APP_HASH=your-fingerprint; lines starting with # serve as comments. For the container to see the file, mount it as a volume, and if it lives somewhere else, point to it with the CRM_APP_ENV_FILE variable.

The file is read at startup and then re-read on the fly: edits in it apply without a restart and are visible to every program the product launches. Settings that are read only at startup (the database, the brand, the product address, and the address and key of the agent) take effect after a restart. If the same setting is given both in the file and in the container environment, the value from the file wins. The product adds the file to the git ignore list itself, so secrets never land in the repository.

Do not confuse this file with the one passed when the container starts with the --env-file flag: there Docker reads the file on your computer and simply sets the variables, while the .env next to the project is read and re-read by the product itself.

Hosting in a subfolder

The product can be served not from the domain root but from a subfolder — for example, https://your-site/crm. There are two ways, and they do not mix.

  • Just name the folder. Set CRM_APP_BASE_PATH=/crm — the product strips the prefix itself and redirects the old addresses to the new form. The domain-check service addresses (/.well-known/) stay at the root.
  • Hand the folder to your proxy. In this case nothing needs changing inside the container: the proxy tells the product the prefix in the X-Forwarded-Prefix header. This is more convenient when you already have nginx or a similar server configured.

In the second case, do not forget the live updates: they need the WebSocket upgrade headers. Two update channels work inside — the general one and the one for the chat with the agent — and both need this upgrade, otherwise the updates quietly turn into rare polling.

# WebSocket upgrade (in the common http {} block)
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 443 ssl;
    server_name host.tld;

    client_max_body_size 100m;                       # file uploads and pasted pictures

    location /crm/ {
        proxy_pass http://crmagent:80/;              # the trailing slash strips the subfolder prefix
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Prefix /crm;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;      # live updates: /appHub and /agentChatHub
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 3600s;                    # long chat answers
        proxy_send_timeout 3600s;
        proxy_buffering off;                         # streaming answers
    }
}

Both ways, and the isolation check between folders, are described in the install/subfolder-hosting.md file next to the product.

First sign-in

Open the interface in a browser. If you set CRM_APP_LOGIN and CRM_APP_PASSWORD, sign in with those; otherwise the first person to register becomes the administrator, after which open registration closes — an outsider can no longer get into your installation. A sign-in session lives 90 days, so you will not have to log in every day. If the product stands behind your organization’s single sign-on (when an external system checks the login), users are created automatically on first visit.

Installing through Xedant MultiAgent

If you have several servers, it is more convenient to install the product from Xedant MultiAgent: it raises the container itself, provides a permanent address and HTTPS, and connects the database. CRM Agent is in the product catalogue (port 6080). MultiAgent passes the product the variables CRM_APP_SSL, CRM_APP_DB and the management key fingerprint CRM_APP_HASH, while the linked Xedant Agent receives the product’s address and key in the variables CRM_APP_URL and CRM_APP_KEY — that is how it reaches the CRM. More about MultiAgent itself is on the MultiAgent page.

License and updates

The trial period is 30 days free, with no limits inside. Prices are the same as the neighboring products: personal $197, company $497, service $970, all lifetime, with 12 months of free updates. Let’s be honest: the license restricts exactly one action — sending a message to the AI agent chat. The client base, the timeline, data quality, import and exports, custom fields, leads, deals, money, marketing, communications, service, screens and management over the API work without it. Details are on the Licensing page.

Next, take a look at Getting Started — a short tour of how the product is built and where to begin.

← Back to the CRM Agent section home