DEV Community

Dmitry Bikishov
Dmitry Bikishov

Posted on

1 1 1 1 1

Ultimate Guide to Setting Up Bun Workspaces with Docker

Introduction

Setting up a monorepo architecture with proper tooling can significantly improve your development workflow. Bun, the all-in-one JavaScript runtime and toolkit, offers excellent workspace support that simplifies managing multiple interconnected packages. This guide walks you through creating a Bun workspace and containerizing it with Docker — perfect for modern full-stack applications.

What You'll Learn

  • How to structure a Bun workspace with shared packages
  • Configuring TypeScript for cross-package imports
  • Containerizing your application with Docker
  • Setting up a complete development environment

Project Structure

Let's start with the folder structure we'll be creating:

├── apps/
│   ├── client/          # Frontend application
│   └── server/          # Backend application
├── infra/
│   ├── docker/          # Docker images
│   │   ├── client.Dockerfile 
│   │   └── server.Dockerfile
├── packages/
│   └── shared/          # Shared code between apps
├── docker-compose.yml 
└── package.json
Enter fullscreen mode Exit fullscreen mode

This structure follows the monorepo pattern where:

  • apps contains your applications (client and server)
  • packages contains shared libraries used across applications
  • infra holds infrastructure-related files like Dockerfiles

Step 1: Initialize the Main Project

First, let's initialize our root project:

bun init -y
Enter fullscreen mode Exit fullscreen mode

Then modify your package.json to configure workspaces:

{
  "name": "workspace",
  "private": true,
  "workspaces": [
    "packages/*",
    "apps/*"
  ],
  "type": "module",
  "devDependencies": {
    "@types/bun": "latest"
  }
}
Enter fullscreen mode Exit fullscreen mode

The workspaces field tells Bun to treat subdirectories in packages and apps as separate packages within our monorepo.

Step 2: Set Up the Shared Package

Create a shared package that will contain code used by both frontend and backend:

mkdir -p packages/shared
cd packages/shared
bun init -y
Enter fullscreen mode Exit fullscreen mode

Update the packages/shared/package.json:

{
  "name": "@packages/shared",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/index.js",
  "scripts": {
    "build": "bun build ./src/index.ts --outdir ./dist --target node"
  }
}
Enter fullscreen mode Exit fullscreen mode

Create a basic source file:

mkdir -p packages/shared/src
echo 'export const greet = (name: string) => `Hello, ${name}!`;' > packages/shared/src/index.ts
Enter fullscreen mode Exit fullscreen mode

Step 3: Set Up the Applications

Backend Application

Set up the server application:

mkdir -p apps/server/src
cd apps/server
bun init -y
Enter fullscreen mode Exit fullscreen mode

Update apps/server/package.json to include the shared package:

{
  "name": "@apps/server",
  "version": "1.0.0",
  "type": "module",
  "dependencies": {
    "@packages/shared": "workspace:*"
  },
  "scripts": {
    "dev": "bun run --watch src/index.ts",
    "start": "bun run src/index.ts"
  }
}
Enter fullscreen mode Exit fullscreen mode

Create a tsconfig.json in the server directory:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "strict": true,
    "paths": {
      "@packages/shared": [
        "../../packages/shared/src"
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Create a simple server:

// apps/server/src/index.ts
import { greet } from '@packages/shared';
import { serve } from 'bun';

const server = serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url);

    if (url.pathname === '/health') {
      return new Response('OK');
    }

    if (url.pathname === '/') {
      return new Response(greet('from the server'));
    }

    return new Response('Not Found', { status: 404 });
  },
});

console.log(`Server running at http://localhost:${server.port}`);
Enter fullscreen mode Exit fullscreen mode

Frontend Application

Set up the client application similarly:

mkdir -p apps/client/src
cd apps/client
bun init -y
Enter fullscreen mode Exit fullscreen mode

Update apps/client/package.json:

{
  "name": "@apps/client",
  "version": "1.0.0",
  "type": "module",
  "dependencies": {
    "@packages/shared": "workspace:*"
  },
  "scripts": {
    "dev": "bun run --watch src/index.ts",
    "start": "bun run src/index.ts"
  }
}
Enter fullscreen mode Exit fullscreen mode

Create a tsconfig.json in the client directory:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "strict": true,
    "paths": {
      "@packages/shared": [
        "../../packages/shared/src"
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Dockerize Your Applications

Server Dockerfile

Create infra/docker/server.Dockerfile:

FROM oven/bun:1-slim

WORKDIR /app

# Copy only package files first
COPY package.json bun.lockb ./
COPY packages/shared/package.json ./packages/shared/
COPY apps/server/package.json ./apps/server/

# Install dependencies
RUN bun install --filter '@apps/server'

# Copy source files
COPY apps/server ./apps/server
COPY packages/shared ./packages/shared

# Build shared package
RUN cd packages/shared && bun run build

EXPOSE 3000

ENTRYPOINT ["bun", "run", "apps/server/src/index.ts"]
Enter fullscreen mode Exit fullscreen mode

Step 5: Create Docker Compose File

Create docker-compose.yml in the root directory:

version: '3.8'

services:
  ...
  server:
    build:
      context: .
      dockerfile: infra/docker/server.Dockerfile
    ports:
      - "3000:3000"
    env_file:
      - apps/server/.env
    restart: unless-stopped
    healthcheck:
      test:
        [
          "CMD",
          "wget",
          "--no-verbose",
          "--tries=1",
          "--spider",
          "http://localhost:3000/health",
        ]
      interval: 30s
      timeout: 10s
      retries: 3
Enter fullscreen mode Exit fullscreen mode

Step 6: Install Dependencies and Run

From the root directory, install all dependencies:

bun install
Enter fullscreen mode Exit fullscreen mode

You can now start everything with Docker Compose:

docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

Or run individual components in development mode:

# Run the server
cd apps/server
bun run dev

# Run the client
cd apps/client
bun run dev
Enter fullscreen mode Exit fullscreen mode

Benefits of This Setup

  1. Code Sharing: The shared package can contain utilities, types, constants, and business logic used by both frontend and backend.

  2. Type Safety: TypeScript configuration ensures type safety across packages.

  3. Development Experience: Changes in shared packages are immediately available to dependent applications.

  4. Production Ready: Docker images are optimized for production deployment.

  5. Scalability: This structure makes it easy to add more applications or packages as your project grows.

Conclusion

You now have a fully functional Bun workspace setup with Docker integration. This architecture promotes code sharing, provides excellent developer experience, and simplifies deployment.

For a complete working example with React frontend and a Bun backend with Prisma, visit the bun-workspaces-docker GitHub repository.

Happy coding with Bun!

Warp.dev image

Warp is the highest-rated coding agent—proven by benchmarks.

Warp outperforms every other coding agent on the market, and gives you full control over which model you use. Get started now for free, or upgrade and unlock 2.5x AI credits on Warp's paid plans.

Download Warp

Top comments (1)

Collapse
 
is_bik profile image
Dmitry Bikishov

Hey there! I'm super excited you found this guide helpful! If you run into any snags while setting up your Bun workspace, drop a comment below and I'll help you out. Don't forget to check out the GitHub repo for more examples. Happy coding! 👋