Skip to content

Conversation

@akoenig
Copy link

@akoenig akoenig commented Sep 11, 2025

This PR adds a complete Docker Compose setup that makes self-hosting Happy Server straightforward. The user only needs to copy the template compose.yml.tmpl over to compose.yml and add a HANDY_MASTER_SECRET.

I adjusted the following:

  • Added an init container for executing the Prisma migrations when the database is healthy so that the user doesn't need to deal with the migrations on their own.
  • Added the missing MinIO container.

@HashWarlock
Copy link

So I ran this in a confidential VM on Phala Cloud, but I had to make some changes due to the prisma migration.

Dockerfile

# Stage 1: Building the application
FROM node:20 AS builder

# Install dependencies
RUN apt-get update && apt-get install -y python3 ffmpeg make g++ build-essential && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Copy package.json and yarn.lock
COPY package.json yarn.lock ./
COPY ./prisma ./prisma

# Install dependencies
RUN yarn install --frozen-lockfile --ignore-engines

# Copy the rest of the application code
COPY ./tsconfig.json ./tsconfig.json
COPY ./vitest.config.ts ./vitest.config.ts
COPY ./sources ./sources

# Build the application
RUN yarn build

# Generate Prisma client
RUN yarn prisma generate

# Stage 2: Runtime
FROM node:20 AS runner

WORKDIR /app

# Install dependencies
RUN apt-get update && apt-get install -y python3 ffmpeg && rm -rf /var/lib/apt/lists/*

# Set environment to production
ENV NODE_ENV=production

# Copy necessary files from the builder stage
COPY --from=builder /app/tsconfig.json ./tsconfig.json
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/sources ./sources
COPY --from=builder /app/prisma ./prisma

# Expose the port the app will run on
EXPOSE 3000

# Command to run migrations then start the app
CMD ["sh", "-c", "yarn prisma migrate deploy && yarn start"]

docker-compose.yml file

services:
  happy-server:
    image: hashwarlock/happy-server:v1.0.1
    ports:
      - "3000:3000"
    restart: unless-stopped
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/happy-server
      - REDIS_URL=redis://redis:6379
      - HANDY_MASTER_SECRET=${HANDY_MASTER_SECRET}
      - PORT=3000
      - S3_HOST=minio
      - S3_PORT=9000
      - S3_USE_SSL=false
      - S3_ACCESS_KEY=minioadmin
      - S3_SECRET_KEY=minioadmin
      - S3_BUCKET=happy
      - S3_PUBLIC_URL=http://minio:9000/happy
    depends_on:
      postgres:
        condition: service_healthy
      minio:
        condition: service_healthy

  postgres:
    image: postgres:15
    environment:
      - POSTGRES_DB=happy-server
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

  minio:
    image: minio/minio
    command: server /data --console-address ":9001"
    ports:
      - "9001:9001"
    environment:
      - MINIO_ROOT_USER=minioadmin
      - MINIO_ROOT_PASSWORD=minioadmin
    volumes:
      - minio_data:/data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3

  minio-init:
    image: minio/mc
    depends_on:
      minio:
        condition: service_healthy
    entrypoint: >
      /bin/sh -c "
      mc alias set myminio http://minio:9000 minioadmin minioadmin;
      mc mb myminio/happy || true;
      mc anonymous set download myminio/happy;
      exit 0;
      "

volumes:
  postgres_data:
  redis_data:
  minio_data:

Ended up working very well. So essentially I have a setup like this:
[CVM: VS-Code Server running Claude] -> [CVM: Happy Server] <- [Mobile Phone Happy App]
Video:
https://youtu.be/ZlED0CjGiKw

@akoenig
Copy link
Author

akoenig commented Sep 12, 2025

@HashWarlock Thanks for checking it out. The changes regarding the migration are not necessary, though. I kept the Dockerfile as-is and instead introduced a migrate service in the compose.yml that boots the application container, mounts the Prisma directory temporarily and executes the migration. When this is done, the respective application container starts.

You can find the migrate definition here.

So basically, you can check out my feature branch and do the following:

cp compose.yml.tmpl compose.yml

# Edit the `HANDY_MASTER_SECRET` in the `compose.yml`

docker compose up -d

# Navigate your browser to http://localhost:3000

@HashWarlock
Copy link

Oh, I should provide some context. We build and publish Docker images in CI and then launch those images inside confidential VMs (CVMs) on Phala Cloud via dstack. That means production runs prebuilt, reproducible images on trusted hardware, so we never docker build on the server. This helps bring transparency to the code running on the server for audits and compliance.

Our setup is stricter than running locally or on a normal VM. On your own machine, you can spin up Docker, run docker compose up -d, and let Prisma migrations run inline at startup. In the confidential VM model, images are immutable, and runtime flexibility is limited (mounts, dependencies, startup order), so containers that try to run migrations dynamically can fail.

Some additional background on my goal is to offer a reproducible developer environment that can be run securely and private by default for dev teams driven by AI. This would be one of our products in our confidential AI marketplace. So my requirements are:

  • Run Coder Server that can use claude
  • Connect to the Coder Server securely through mobile & have identified options:
    • web shell (rendering on mobile through the web has mixed results across different devices)
    • ssh in with termix (pain points around UX & ssh connection to CVM not reliable)
    • use happy mobile app and deploy a happy relayer in CVM then install happy-coder on the Coder Server to set the relayed to the CVM URL

From my investigation, the best UX was through Happy which is the video I uploaded. We could meet to discuss more if there is any interest in this solution, but no pressure. I am a really big fan of what you built & will be tinkering with the solution to fit our needs. Thanks again for building such a great product to help enhance confidential AI.

@ex3ndr
Copy link
Contributor

ex3ndr commented Sep 13, 2025

Why you need this level of protection?

@rrnewton
Copy link

This looks fabulous. I'll give it a try. I made a much more basic setup for running/testing/extending all three happy components locally:

slopus/happy#221

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants