Installation

Don’t want to read all this?

Just drop a link to https://xedant.com/agents/portal/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.

Portal Agent is the program your company portal lives in: pages, blocks, live numbers from your systems and a public site. All of it deploys on your server as a single Docker container. Service data (the database, the license, unread marks) lives in the /apps/portal volume, while the portal content itself is a separate /project/apps/portal folder — handy to keep in git. No separate database is needed: the built-in SQLite works out of the box, and PostgreSQL connects optionally. You do not need to install Python either — it is already inside the image, so dynamic blocks work at once.

Minimum requirements

Any Linux server with Docker and Docker Compose is enough (an inexpensive VPS will do). If you prefer working on Windows, install Docker Desktop — the walkthrough lives on the Windows Setup page. Two things need separate care: a permanent volume for the service data and a folder for the portal content. Without the volume, the database and the license disappear the first time the container is recreated; the content is handier to keep in a repository folder under git — then it does not depend on the container and moves by simple copying.

The product is economical with the server’s resources. When idle, the CPU is not loaded: while nobody is working with the portal the application polls nothing on a timer — updates arrive by themselves when something changes. After 15 minutes without activity the memory in use is released — calculation results, assembled pages, check results and the page search index — and everything comes back by itself on the next visit. That is why Portal Agent sits comfortably next to other services on the same server.

What is already built into the image, with nothing to install:

  • the application web server (ASP.NET Core 9);
  • an SQLite database — works without any setup;
  • a Python environment for dynamic blocks and scripts — it creates itself on first run;
  • for the chat with the AI agent you will need the address and key of your Xedant Agent — but that is optional: without them everything works except the chat itself.

Docker Compose

Create a compose.yml file and run docker compose up -d:

services:
  portalagent:
    image: xedant/portal-agent:min-latest
    container_name: portalagent
    ports:
      - "3992:80"
    volumes:
      # /apps/portal — service data: the database, keys, license, Python environment
      - portalagent-data:/apps/portal
      # Portal content is not baked into the image: by default it is looked up in /project/apps/portal.
      # Mount your git-managed content folder here; without it the portal comes up empty.
      # - ./apps/portal:/project/apps/portal
      # Settings file .env from the project folder root (see the "The .env file" section below):
      # - ./.env:/project/.env
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - ASPNETCORE_URLS=http://+:80
      - PORTAL_APP_DATA=/apps/portal
      # PORTAL_APP_BASE_PATH=/portal  # serve from a first-level subfolder (see ../subfolder-hosting.md)
    restart: unless-stopped
    sysctls:
      fs.inotify.max_user_watches: "524288"
      fs.inotify.max_user_instances: "512"

volumes:
  portalagent-data:

After startup the interface is available on port 3992: http://<server-address>:3992.

The two sysctls lines raise the system file-watching limits — they are needed so page edits made by the agent appear on an open page at once, without a reload. Without them, on a large portal some changes may not reach the reader.

If the portal should live not at the domain root but in a subfolder (for example, /portal), uncomment the PORTAL_APP_BASE_PATH=/portal line — the application will re-shape all its addresses accordingly.

Environment variables

Application settings are passed through environment variables — a convenient way to set them once in compose.yml and change them without touching the code. All variables except AGENT_* start with PORTAL_APP_.

VariablePurposeDefault
PORTAL_APP_BRANDbrand and interface language: xedant (English) or pastukhov (Russian)xedant
PORTAL_APP_DATAservice data folder: the database, keys, license, Python environment/apps/portal
PORTAL_APP_DBdatabase: a PostgreSQL connection string; empty value — the built-in SQLiteSQLite (/apps/portal/data.db)
PORTAL_APP_PORTAL_PATHfolder with the portal content — a git repository folder, separate from the service data/project/apps/portal
PORTAL_APP_HASHSHA-256 hash of the key the AI agent uses to manage the portal (the X-API-Key header); without it management is disabled and answers 503— (management disabled)
PORTAL_APP_LOGINlogin of a pre-configured administrator; together with the password, creates the user at startup— (self-registration)
PORTAL_APP_PASSWORDlowercase SHA-256 hash of the password, not the password itself—
PORTAL_APP_BASE_PATHserving from a first-level subfolder (for example, /portal)— (domain root)
PORTAL_APP_SSLyour own self-signed HTTPS: auto — enable; companion vars _SSL_PORT and _SSL_DOMAINS— (HTTP only)
PORTAL_APP_ERROR_REPORTING0 — disable sending error reports to the developer— (sending enabled)
PORTAL_APP_ENV_FILE0 — do not read the .env file from the project folder root (see below)— (the file is read)
AGENT_API_URLaddress of the Xedant Agent that runs the portal: the chat and edits in plain words go through it— (agent chat hidden)
AGENT_API_KEYaccess key to your Xedant Agent—
AGENT_LICENSElicense key, so you do not have to enter it by hand in the interface— (the license file from the volume is used)

Note: PORTAL_APP_HASH holds not the key itself but its hash — a “fingerprint” from which the key cannot be recovered. Compute it with printf '%s' 'your-key' | sha256sum and paste it into the variable. The password works the same way: PORTAL_APP_PASSWORD receives its lowercase SHA-256 hash (echo -n your-password | sha256sum), and on sign-in the application hashes the entered password and compares it with the configured value.

A separate note about error reports. By default the application sends the developer a report when something breaks — this is how the problem becomes known and can be fixed. Nothing needs to be configured for that. If sending is undesirable, set PORTAL_APP_ERROR_REPORTING=0 — the application will stop sending such reports.

The .env file — settings and secrets in one place

Fine-grained settings and secrets are handier to keep not in compose.yml but in a separate .env file in the root of the project folder — next to the portal content folder (by default /project/.env). The format is the simplest: a name and a value per line, and a # starts a comment.

# the hash of the key the agent uses to manage the portal
PORTAL_APP_HASH=e3b0c44298fc1c149afbf4c8996fb924...

# connection to your Xedant Agent
AGENT_API_URL=https://agent.your-domain.com
AGENT_API_KEY=agent-key

What matters about this file:

  • Read at startup. Values from the real environment — those set in compose.yml or with the -e flag — take precedence: the file only fills in what they lack.
  • Edits apply on the fly. The file re-reads itself within a second: the management key, subfolder placement, the data folder and sign-in through a shared proxy can all be changed without a restart. Settings that are read once at startup (the database, the agent address and key, the license, the brand) are picked up after the container restarts.
  • Scripts see the values. Dynamic blocks run in the same environment, so addresses and passwords for your systems are conveniently passed to scripts this way rather than stored in the content.
  • The file stays yours. It usually holds keys, so it never gets into git: the repository and the version history will not see it.
  • Can be disabled. The PORTAL_APP_ENV_FILE=0 variable turns off reading the file entirely — then only environment variables work.

Inside the container the file has to be mounted, otherwise it has nowhere to come from: uncomment the - ./.env:/project/.env line in the example above, or mount the whole repository as - .:/project. When the file lives in another folder, point to it with the PORTAL_APP_PROJECT_FOLDER variable.

Where the data lives

The portal’s data is split into two parts, and that is convenient. The service state lives in the /apps/portal volume and survives container recreation:

  • /apps/portal/data.db — the SQLite database (when PostgreSQL is not used);
  • /apps/portal/auth/ — data protection keys that sign sign-in sessions;
  • /apps/portal/.xedant/ — the license (under the pastukhov brand the folder is called .pastukhov);
  • /apps/portal/portal-data/ — the scripts’ working folder (temporary files, calculation results);
  • /apps/portal/.venv — the Python environment where dynamic blocks run;
  • /apps/portal/portal-unread.txt — the “unread” marks for portal pages;
  • /apps/portal/search-index/ — the page search index: page text lives in files on disk, so search works right after a restart and does not have to walk the whole portal again.

The content itself lives separately — in the /project/apps/portal folder, and that is not a volume but your repository folder: inside there is one folder per page, block texts and images. The state volume is mandatory: without it, the first docker compose down would lose the database and the license. The content, in turn, does not depend on the container: the folder can be kept in git whole, its edit history stays visible, and it moves to another server by simple copying. In compose it is mounted at /project/apps/portal; when nothing is mounted, the portal comes up empty, and the pages you create will not survive container recreation.

First sign-in

Open the interface in a browser. While there is not a single user in the database, the registration form is open by itself — the first person to register becomes the administrator. If the login and password are set through PORTAL_APP_LOGIN and PORTAL_APP_PASSWORD, sign in with them (the variable stores the hash; at sign-in you enter the password itself). The sign-in session lives for 90 days. When the application is deployed together with other products, sign-in can go through the server’s shared sign-in — it is configured with the SSO_PROXY_TOKEN variable.

What to do after startup

Once the interface is open, what remains is to connect the managing agent and create the first pages:

  1. Connect Xedant Agent — set AGENT_API_URL and AGENT_API_KEY. After that the chat with the AI agent and the “Edit with agent” buttons on the page and on blocks appear in the interface.
  2. Set the portal management key — the PORTAL_APP_HASH variable with the key’s hash. Through this address the agent creates and edits pages, files and scripts.
  3. Ask the agent to create the first pages — just describe in words what you need: “make a News section with a feed and a Contacts page”.
  4. Assign access to the pages: a new page is a “draft” by default, visible to the administrator only.
  5. Mark the pages with “public” access — they will appear on the outside site at /p/.

More about the first steps is in the Getting Started section.

Running without Compose

You can also start the application with a single docker run command:

docker run -d \
  --name portalagent \
  -p 3992:80 \
  -v portalagent-data:/apps/portal \
  -v ./apps/portal:/project/apps/portal \
  -e PORTAL_APP_DATA=/apps/portal \
  --sysctl fs.inotify.max_user_watches=524288 \
  --sysctl fs.inotify.max_user_instances=512 \
  --restart unless-stopped \
  xedant/portal-agent:min-latest

The remaining environment variables can be passed through a file with the --env-file .env flag — handier when there are many settings. That file belongs to Docker itself: it passes the values to the container once at startup. Do not confuse it with the .env file from the project folder root: that one is read by the application itself and re-reads on the fly, without a restart (see the “The .env file” section above).

Installing through Xedant MultiAgent

If you have several servers and applications, it is handier to install the products from Xedant MultiAgent: it raises the container itself, issues a permanent address and HTTPS, connects the database and a shared documents volume. Portal Agent is in the MultiAgent catalog: in the installation wizard you pick an existing agent on this server or create a new one, and the linked agent gets a ready-made portal management skill right away. Details are on the MultiAgent page.

License

The license key is activated in the license dialog in the interface, or passed through the AGENT_LICENSE variable at startup. There is no ready license in the image: a fresh container starts without one. Everything works without a license — the portal tree and pages, blocks, dynamic blocks on Python, forms, the public site and content edits by the agent. The license is needed only for sending messages in the chat with the AI agent. Details are on the Licensing page.

← Back to the Portal Agent section home