Self-hosted deployment
For complete control over your bot's environment, you can self-host it on your own server. This guide deploys a BotKit bot on a Linux server with SSH access, puts Caddy in front of it for HTTPS, and keeps it running under systemd. It covers both the Deno and Node.js runtimes; follow the one your bot uses.
Prerequisites
- A Linux server with SSH access
- A domain name whose DNS points to your server
- Either Deno or Node.js installed on your server, matching your bot
Installation steps
Install the runtime your bot uses:
curl -fsSL https://deno.land/install.sh | sh# Install the current LTS from nodejs.org, your distribution, or a version # manager such as fnm. For example, with fnm: curl -fsSL https://fnm.vercel.app/install | bash fnm install --ltsClone your bot repository:
git clone https://github.com/yourusername/your-bot.git cd your-botSet up environment variables:
echo 'export SERVER_NAME="your-domain.com"' >> ~/.bashrc source ~/.bashrc
Setting up HTTPS with Caddy
Caddy is recommended for handling HTTPS and reverse proxy. It automatically obtains and renews SSL/TLS certificates from Let's Encrypt without any manual configuration:
# Install required packages sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https # Add Caddy repository curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \ sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \ sudo tee /etc/apt/sources.list.d/caddy-stable.list # Install Caddy sudo apt update sudo apt install caddy# Add Caddy repository dnf install 'dnf-command(copr)' dnf copr enable @caddy/caddy # Install Caddy dnf install caddy# Install from official repositories sudo pacman -S caddyTIP
For other installation methods or operating systems, see the official Caddy installation docs.
Create a Caddyfile at /etc/caddy/Caddyfile:
your-domain.com { # Automatic HTTPS tls { # Email for Let's Encrypt notifications email your-email@example.com } # Proxy all requests to your bot reverse_proxy localhost:8000 # Basic security headers header { # Enable HSTS Strict-Transport-Security "max-age=31536000;" # Prevent clickjacking X-Frame-Options "DENY" # XSS protection X-Content-Type-Options "nosniff" X-XSS-Protection "1; mode=block" } }NOTE
Make sure your domain's DNS A record points to your server's IP address before starting Caddy. Caddy will automatically obtain and manage SSL certificates from Let's Encrypt.
Set proper ownership and permissions:
sudo chown root:root /etc/caddy/Caddyfile sudo chmod 644 /etc/caddy/CaddyfileRestart Caddy:
sudo systemctl restart caddyCheck Caddy's status:
sudo systemctl status caddy sudo journalctl -u caddy --follow
IMPORTANT
Caddy terminates TLS and forwards requests to the bot over plain HTTP, so the bot has to trust the X-Forwarded-* headers to reconstruct its public HTTPS origin. Turn on behindProxy in createBot(). The systemd units in the next section export BEHIND_PROXY=true for the bot to read into that option, following the pattern in Exposing the bot to the public internet.
Creating a systemd service
The systemd is a system and service manager for Linux that starts and manages services. It is adopted by most modern Linux distributions including Debian, Ubuntu, Fedora, and Arch Linux.
NOTE
A self-hosted bot runs as one long-lived process, so BotKit starts its message queue automatically once the first task is enqueued. You don't need to start the queue worker yourself.
To run your bot as a systemd service:
Create a dedicated user for your bot (recommended for security):
sudo useradd -r -s /bin/false botkitSet up the bot's directory:
sudo mkdir -p /opt/botkit sudo chown botkit:botkit /opt/botkitCreate a service file at /etc/systemd/system/botkit-bot.service. The unit is the same for both runtimes apart from
ExecStart, which starts the bot the way its runtime expects (see Running the bot):[Unit] Description=BotKit Bot After=network.target Wants=caddy.service [Service] Type=simple User=botkit Group=botkit Environment=SERVER_NAME=your-domain.com # Add any other environment variables your bot needs Environment=NODE_ENV=production Environment=BEHIND_PROXY=true WorkingDirectory=/opt/botkit # A bot uses `export default bot`, so start it with `deno serve`, not # `deno run`. ExecStart=/usr/local/bin/deno serve -A --port 8000 bot.ts # Restart policy Restart=always RestartSec=10 # Security settings NoNewPrivileges=true ProtectSystem=strict ProtectHome=true PrivateTmp=true PrivateDevices=true # Resource limits CPUQuota=80% MemoryMax=1G [Install] WantedBy=multi-user.target[Unit] Description=BotKit Bot After=network.target Wants=caddy.service [Service] Type=simple User=botkit Group=botkit Environment=SERVER_NAME=your-domain.com # Add any other environment variables your bot needs Environment=NODE_ENV=production Environment=BEHIND_PROXY=true WorkingDirectory=/opt/botkit # Serve the `export default bot` entry through the srvx CLI, whose binary # ships with the bot's dependencies under node_modules. ExecStart=/opt/botkit/node_modules/.bin/srvx serve --prod --port 8000 --entry bot.ts # Restart policy Restart=always RestartSec=10 # Security settings NoNewPrivileges=true ProtectSystem=strict ProtectHome=true PrivateTmp=true PrivateDevices=true # Resource limits CPUQuota=80% MemoryMax=1G [Install] WantedBy=multi-user.targetThe unit hardens the service with
ProtectHome=true, so it cannot read anything under /home. Install the runtime (thedenoornodebinary) somewhere system-wide, such as /usr/local/bin, so thatExecStartand, on Node.js, srvx's#!/usr/bin/env nodelauncher can find it.NOTE
On Node.js, add the srvx package to the project's dependencies and install them in /opt/botkit (for example, with
npm ci), so that /opt/botkit/node_modules/.bin/srvx exists before the service starts. The bot.ts file should end withexport default bot, exactly as on Deno.Set proper permissions:
sudo chown root:root /etc/systemd/system/botkit-bot.service sudo chmod 644 /etc/systemd/system/botkit-bot.serviceReload systemd, enable and start the service:
# Reload systemd to recognize the new service sudo systemctl daemon-reload # Enable service to start on boot sudo systemctl enable botkit-bot # Start the service sudo systemctl start botkit-botVerify the service is running:
# Check service status sudo systemctl status botkit-bot # View logs sudo journalctl -u botkit-bot -f
TIP
To update your bot:
- Copy new files to /opt/botkit
- Set proper ownership:
sudo chown -R botkit:botkit /opt/botkit - Restart the service:
sudo systemctl restart botkit-bot
Monitoring logs
Monitor your bot's logs using systemd:
sudo journalctl -u botkit-bot -f