BasisBasis
Getting Started

Installation

Deploy Basis Server Edition on Linux, Windows Server, or Docker. The package is self-contained — no .NET runtime needs to be installed separately.

Linux Server

Tested on Ubuntu 22.04 / 24.04, Debian 12, Rocky Linux 9. The package is a self-contained x64 binary — no .NET runtime installation required.

Requirements

  • Linux x64 (amd64)
  • unzip to extract the archive
  • Nginx (recommended) for HTTPS and reverse proxy
  • A writable data directory (e.g., /var/basis/data)

Step 1 — Create a Dedicated User

Running as a non-root user is strongly recommended:

sudo useradd -r -s /bin/false basis

Step 2 — Extract the Package

sudo mkdir -p /opt/basis
sudo unzip BasisServer-v1.0-linux-x64.zip -d /opt/basis
sudo chmod +x /opt/basis/Basis.Web
sudo chown -R basis:basis /opt/basis

Step 3 — Create the Data Directory

sudo mkdir -p /var/basis/data/businesses
sudo mkdir -p /var/basis/data/attachments
sudo chown -R basis:basis /var/basis

Step 4 — Configure

sudo cp /opt/basis/appsettings.Custom.json.template /opt/basis/appsettings.Custom.json
sudo nano /opt/basis/appsettings.Custom.json

Edit the file with your values:

{
  "Urls": "http://+:8080",
  "LicenseOptions": {
    "Edition": "Server",
    "Key": "YOUR-LICENSE-KEY-HERE"
  },
  "DatabaseOptions": {
    "BasePath": "/var/basis/data"
  },
  "JwtSettings": {
    "SecretKey": "CHANGE-THIS-TO-A-STRONG-RANDOM-STRING-MIN-32-CHARS",
    "Issuer": "Basis",
    "Audience": "BasisUsers"
  }
}

Restrict permissions on the config file (it contains secrets):

sudo chown basis:basis /opt/basis/appsettings.Custom.json
sudo chmod 640 /opt/basis/appsettings.Custom.json

Step 5A — Test Run (manual)

Before setting up the service, confirm the app starts cleanly:

cd /opt/basis
sudo -u basis ./Basis.Web

Open a browser and go to http://<server-ip>:8080. Stop with Ctrl+C.

Step 5B — Install as systemd Service (recommended)

sudo cp /opt/basis/basis.service /etc/systemd/system/basis.service
sudo systemctl daemon-reload
sudo systemctl enable basis
sudo systemctl start basis
sudo systemctl status basis

The bundled basis.service file:

[Unit]
Description=Basis Accounting Server
After=network.target

[Service]
Type=notify
WorkingDirectory=/opt/basis
ExecStart=/opt/basis/Basis.Web \
    --urls "http://+:8080" \
    --DatabaseOptions:BasePath /var/basis/data
Restart=on-failure
RestartSec=10
User=basis
Group=basis

[Install]
WantedBy=multi-user.target
The --urls and --DatabaseOptions:BasePath arguments on ExecStart override appsettings.Custom.json. Remove them from ExecStart if you prefer to control everything via the config file.

Service management commands

sudo systemctl start   basis     # start
sudo systemctl stop    basis     # stop
sudo systemctl restart basis     # restart
sudo systemctl status  basis     # status

sudo journalctl -u basis -f                      # follow live logs
sudo journalctl -u basis --since "1 hour ago"    # recent logs

Step 6 — Firewall

UFW (Ubuntu / Debian):

sudo ufw allow 8080/tcp
sudo ufw reload

firewalld (Rocky Linux / RHEL / CentOS):

sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload
In production, expose only port 443 (HTTPS) to the public internet via Nginx. Port 8080 only needs to be accessible from localhost (Nginx → Basis). Do not open port 8080 in your cloud firewall/security group if Nginx is on the same server.

Step 7 — Nginx Reverse Proxy + HTTPS

Install Nginx:

sudo apt install nginx       # Ubuntu / Debian
sudo dnf install nginx       # Rocky / RHEL

Create a site config at /etc/nginx/sites-available/basis:

sudo nano /etc/nginx/sites-available/basis
server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name yourdomain.com;

    ssl_certificate     /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    location / {
        proxy_pass          http://localhost:8080;
        proxy_http_version  1.1;
        proxy_set_header    Upgrade $http_upgrade;
        proxy_set_header    Connection keep-alive;
        proxy_set_header    Host $host;
        proxy_set_header    X-Real-IP $remote_addr;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto $scheme;
        proxy_cache_bypass  $http_upgrade;

        # Required for Blazor SignalR long-lived connections
        proxy_read_timeout  3600;
        proxy_send_timeout  3600;
    }
}

Enable the site and reload Nginx:

sudo ln -s /etc/nginx/sites-available/basis /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Free SSL certificate with Let's Encrypt:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com

Updating

sudo systemctl stop basis

# Replace app files — config and data are NOT touched
sudo unzip -o BasisServer-v<new-version>-linux-x64.zip -d /opt/basis
sudo chmod +x /opt/basis/Basis.Web
sudo chown -R basis:basis /opt/basis

sudo systemctl start basis

appsettings.Custom.json and all data in /var/basis/data are never overwritten.

Uninstalling

sudo systemctl stop    basis
sudo systemctl disable basis
sudo rm /etc/systemd/system/basis.service
sudo systemctl daemon-reload
sudo rm -rf /opt/basis

# Optional — remove data (irreversible)
sudo rm -rf /var/basis
sudo userdel basis

Windows Server

Supported on Windows Server 2019 / 2022 and Windows 10 / 11 (64-bit). The package includes the .NET runtime — no separate installation needed.

Step 1 — Extract the Package

Extract BasisServer-v1.0-win-x64.zip to a permanent folder, e.g. C:\Basis\. The folder should contain Basis.Web.exe and supporting files.

Step 2 — Configure

Rename the template file:

appsettings.Custom.json.template  →  appsettings.Custom.json

Edit appsettings.Custom.json with Notepad or any text editor:

{
  "Urls": "http://+:8080",
  "LicenseOptions": {
    "Edition": "Server",
    "Key": "YOUR-LICENSE-KEY-HERE"
  },
  "DatabaseOptions": {
    "BasePath": "C:\\BasisData"
  },
  "JwtSettings": {
    "SecretKey": "CHANGE-THIS-TO-A-STRONG-RANDOM-STRING-MIN-32-CHARS",
    "Issuer": "Basis",
    "Audience": "BasisUsers"
  }
}

Create the data folder:

# PowerShell
New-Item -ItemType Directory -Force -Path "C:\BasisData\businesses"
New-Item -ItemType Directory -Force -Path "C:\BasisData\attachments"

Step 3A — Test Run (manual)

cd C:\Basis
.\Basis.Web.exe

Open a browser and go to http://localhost:8080. Stop with Ctrl+C.

Step 3B — Install as Windows Service (recommended)

Run in an Administrator PowerShell:

sc create "BasisServer" `
  binPath= "C:\Basis\Basis.Web.exe" `
  DisplayName= "Basis Accounting Server" `
  start= auto

sc description "BasisServer" "Basis Server Edition — multi-tenant accounting system"

sc start "BasisServer"
Port and data path are read from appsettings.Custom.json in the app folder. You can also pass them as command-line arguments directly in binPath:
binPath= "C:\Basis\Basis.Web.exe --urls http://+:8080 --DatabaseOptions:BasePath C:\BasisData"

Verify the service is running:

sc query "BasisServer"

Service management commands

sc start  "BasisServer"    # start
sc stop   "BasisServer"    # stop
sc delete "BasisServer"    # uninstall (stop first)

View logs in Event Viewer → Windows Logs → Application → Source: BasisServer.

Step 4 — Firewall

Allow inbound connections on the app port (if accessing from other machines on the network):

# PowerShell (Administrator)
New-NetFirewallRule -DisplayName "Basis Server" `
  -Direction Inbound -Protocol TCP -LocalPort 8080 -Action Allow

Step 5 — Reverse Proxy (optional, for HTTPS)

Place IIS or Nginx in front of Basis to terminate HTTPS and proxy to http://localhost:8080.

IIS ARR reverse proxy — create a new IIS site and add a web.config:

<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="ReverseProxy" stopProcessing="true">
          <match url="(.*)" />
          <action type="Rewrite" url="http://localhost:8080/{R:1}" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Requires the IIS URL Rewrite and Application Request Routing (ARR) modules.

Updating

  1. Stop the service: sc stop "BasisServer"
  2. Replace all files in C:\Basis\ with the new version. Keep appsettings.Custom.json — do not overwrite it.
  3. Start the service: sc start "BasisServer"

Data in C:\BasisData (or your configured path) is never touched during an update.

Uninstalling

sc stop   "BasisServer"
sc delete "BasisServer"
Remove-Item "C:\Basis" -Recurse -Force

# Optional — remove data (irreversible)
Remove-Item "C:\BasisData" -Recurse -Force

Docker

The quickest way to get a server running. The image is a self-contained Cloud edition build.

Quick Start

docker run -d \
  --name basis \
  -p 8080:8080 \
  -v /your/data:/data \
  -e JwtSettings__SecretKey="CHANGE-THIS-MIN-32-CHARS" \
  -e LicenseOptions__Edition="Cloud" \
  ghcr.io/basis-app/basis:latest

Open http://localhost:8080 in a browser.

Docker Compose

Save as docker-compose.yml:

services:
  basis:
    image: ghcr.io/basis-app/basis:latest
    container_name: basis
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./data:/data
    environment:
      ASPNETCORE_ENVIRONMENT: Production
      DatabaseOptions__BasePath: /data

      # REQUIRED — change before deploying
      JwtSettings__SecretKey: "CHANGE-THIS-TO-A-STRONG-SECRET-AT-LEAST-32-CHARS"
      JwtSettings__Issuer: Basis
      JwtSettings__Audience: BasisUsers

      # Edition & License
      LicenseOptions__Edition: Cloud     # Cloud | Server
      LicenseOptions__Key: ""            # Server edition only
docker-compose up -d
docker-compose logs -f basis
Always mount a host volume to /data. If you stop the container without a volume, all business data is lost when the container is removed.

Configuration Reference

All configuration can be set in appsettings.Custom.json (file-based) or via environment variables (replace : with __, e.g., JwtSettings__SecretKey). Environment variables take precedence.

KeyRequiredNotes
UrlsRequiredListening address and port. Use http://+:8080 for all interfaces. Change port if 8080 is occupied.
JwtSettings:SecretKeyRequiredSecret used to sign session tokens. Must be at least 32 characters. Use a randomly generated string. Never commit to source control.
JwtSettings:IssuerOptionalJWT issuer claim. Default: Basis.
JwtSettings:AudienceOptionalJWT audience claim. Default: BasisUsers.
DatabaseOptions:BasePathOptionalFolder for all SQLite database files. Defaults: Docker → /data, Azure App Service → /home/basis/data, other → {app-dir}/Data.
LicenseOptions:EditionRequiredServer or Cloud. Hardcoded to Server in Basis.Web; set to Cloud in Docker image.
LicenseOptions:KeyOptionalLicense key for Server edition. App runs normally without a key; only a renewal banner is shown.