Deploying
A Ruby bot is a normal Python process. Run it however you like, as long as it starts in the bot's folder, with ruby or python -m ruby.
Containers
A two-stage build with uv keeps the image small. This is what one bot uses in the Containerfile:
dockerfile
# ---- Build stage ----
FROM ghcr.io/astral-sh/uv:python3.14-trixie-slim AS builder
# git, so uv can fetch Ruby from Codeberg
RUN apt-get update \
&& apt-get install -y --no-install-recommends git ca-certificates \
&& rm -rf /var/lib/apt/lists/*
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy UV_NO_DEV=1 UV_PYTHON_DOWNLOADS=0
WORKDIR /app
# Dependencies first, so code changes don't reinstall everything
COPY pyproject.toml uv.lock ./
RUN uv sync --locked --no-install-project
COPY . .
RUN uv sync --locked --no-editable
# ---- Runtime stage ----
FROM python:3.14-slim-trixie
ENV PYTHONUNBUFFERED=1 PATH="/app/.venv/bin:$PATH"
WORKDIR /app
RUN groupadd --system app && useradd --system --gid app --home-dir /app app
COPY --from=builder --chown=app:app /app /app
USER app
CMD ["ruby"]And a docker-compose.yml file with MongoDB:
yaml
services:
mongo:
image: docker.io/library/mongo:8
restart: unless-stopped
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_USERNAME:?Set MONGO_USERNAME in .env}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD:?Set MONGO_PASSWORD in .env}
volumes:
- mongo_data:/data/db
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"]
interval: 10s
retries: 5
bot:
build: .
restart: on-failure
depends_on:
mongo:
condition: service_healthy
env_file: .env # TOKEN, MONGO_URI=mongodb://user:password@mongo:27017
volumes:
mongo_data:config.yaml is baked into the image
With the Containerfile above, config.yaml and cogs/ are copied in at build time, so changing the config means rebuilding. Recreate the container afterwards too: with podman-compose, up -d keeps the old container unless you add --force-recreate.
sh
podman-compose build bot && podman-compose up -d --force-recreate --no-deps botTo skip rebuilding for config changes, mount it instead: volumes: ["./config.yaml:/app/config.yaml:ro"].
Updating Ruby
uv pins Ruby to a commit in uv.lock. To pick up a new version:
sh
uv lock --upgrade-package rubyThen rebuild.
systemd (no container)
ini
[Unit]
Description=Ruby Bot
After=network-online.target
[Service]
WorkingDirectory=/opt/ruby-bot
ExecStart=/opt/ruby-bot/.venv/bin/python -m ruby
Restart=on-failure
User=rubybot
[Install]
WantedBy=multi-user.target