All case studies

AI / Self-Hosted

A Conversational Bot That Runs in One Container, No External Services

Frontend, backend, database, LLM client all bundled together. Deploys to Docker or Railway as a single container. Drop it into any environment without adding infra requirements.

GSGreyfeathers Studios2 min read
A Conversational Bot That Runs in One Container, No External Services
AI / Self-Hosted. April 10, 2026.

Most chatbots require an entire infrastructure stack to run. A database service, a backend service, a frontend deployment, a separate hosting tier for the LLM proxy. For internal tools or customer-deployed integrations, that overhead is a deal-breaker.

The brief was a self-contained bot. One container that holds the frontend, the backend, the database, and the LLM client. Deploy it anywhere a container runs. No external services to provision.

...

What we built

1. Single-process architecture

Express serves the API. The same Express process also serves the built React frontend as static files. SQLite handles persistence inside the container. The only external call is to the LLM provider.

  • Express backend serving both API and static frontend
  • SQLite for conversation history
  • React frontend built and served by the same process
  • Single container deployment to Docker or Railway

2. Conversation surface

The chat UI is the usual streaming-message pattern. Messages persist to SQLite so conversation history survives a container restart.

  • Streaming message rendering as tokens arrive
  • Conversation history persisted across sessions
  • File upload support for context attachments
  • Lightweight UI with no external font or asset dependencies

3. Pluggable LLM client

The LLM call sits behind a thin abstraction. Groq is the default for speed. Swapping in OpenAI or Anthropic is a configuration change, not a code change.

  • Provider-agnostic LLM client
  • Groq as the default for fast inference
  • Swappable to OpenAI or Anthropic via config

Tech stack and why we chose it

Self-contained was the constraint. Every choice was made to honour it.

  • Express for the backend. The simplest mature Node framework. Serves API and static files from one process without ceremony.
  • SQLite for persistence. A single file inside the container. No separate database service to run. Performance is more than adequate for conversation history.
  • React with Vite for the frontend. Vite produces a fast, small build that Express serves as static files. No separate frontend deployment.
  • Multer for file upload handling. Standard Express middleware.
  • Groq SDK for the LLM client. Fastest inference at the price point. The thin abstraction around it makes swapping providers trivial.
  • Lucide React for iconography. Small bundle impact, consistent design.
  • Docker or Railway for deployment. One container, one process, one binary destination.

Outcome

Deployable as a single artifact. Drop it into any environment that runs a container; it works.

...

Frequently asked questions

What about scale?

For internal tools and per-customer deployments, a single container is the right scale. If a deployment outgrows it, the architecture allows splitting the SQLite layer out to a managed database and replicating the container behind a load balancer. We deferred that complexity until needed.

How is conversation history backed up?

The SQLite file is a single artefact. Backups are file copies. Restore is replacing the file. Simpler than a managed database for the scale this product runs at.