Files
TeleUploader/docs/superpowers/plans/2026-05-18-deploy-plan.md
T

8.3 KiB

Docker, Traefik, and GitHub Actions Deployment Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Containerize TeleUploader using Bun, configure Traefik labels for routing upload.asepharyana.tech, and set up full GitHub Actions CI/CD to VPS 45.127.35.244.

Architecture: Use a multi-stage Dockerfile with Bun to bundle and run the application. Serve via docker-compose.yml linking to external Traefik network app-shared-net. Deploy via GitHub Action workflow SSH using GitHub secrets for secure configuration.

Tech Stack: Bun 1.1, Docker, Docker Compose, Traefik, GitHub Actions.


Task 1: Dockerfile Setup

Files:

  • Create: Dockerfile

  • Create: .dockerignore

  • Step 1: Create .dockerignore to prevent copying unnecessary files Write to /.dockerignore:

node_modules
.git
.github
docs
logs
dist
.env
  • Step 2: Create multi-stage Dockerfile using Bun Write to /Dockerfile:
# Stage 1: Build the application
FROM oven/bun:1.1-alpine AS builder

WORKDIR /usr/src/app

# Copy dependency configuration files
COPY package.json bun.lock ./

# Install dependencies
RUN bun install --frozen-lockfile

# Copy source and configurations
COPY tsconfig.json biome.json ./
COPY src ./src

# Lint, format and build
RUN bun run lint
RUN bun run build

# Stage 2: Final minimal production environment
FROM oven/bun:1.1-slim AS runner

WORKDIR /usr/src/app

# Set production environment variables
ENV NODE_ENV=production
ENV PORT=3000

# Copy necessary files from builder and repo
COPY --from=builder /usr/src/app/dist/index.js ./dist/index.js
COPY --from=builder /usr/src/app/package.json ./package.json
COPY schema.sql ./schema.sql

# Expose server port
EXPOSE 3000

# Start server
CMD ["bun", "dist/index.js"]
  • Step 3: Test Docker build locally Run: docker build -t teleuploader:test . Expected: Successfully builds without errors.

  • Step 4: Commit Dockerfile changes Run:

git add Dockerfile .dockerignore
git commit -m "chore: add Dockerfile and dockerignore for production build"

Task 2: Docker Compose Setup

Files:

  • Create: docker-compose.yml

  • Step 1: Write docker-compose.yml with external Traefik network configuration Write to /docker-compose.yml:

version: '3.8'

services:
  app:
    image: ghcr.io/mytheclipse/teleuploader:latest
    container_name: teleuploader-app
    restart: always
    environment:
      - BOT_TOKEN=${BOT_TOKEN}
      - STORAGE_CHANNEL_ID=${STORAGE_CHANNEL_ID}
      - BASE_URL=${BASE_URL}
      - DATABASE_URL=${DATABASE_URL}
      - PORT=3000
      - NODE_ENV=production
      - LOG_LEVEL=info
    networks:
      - app-shared-net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.teleuploader.rule=Host(`upload.asepharyana.tech`)"
      - "traefik.http.routers.teleuploader.entrypoints=websecure"
      - "traefik.http.routers.teleuploader.tls=true"
      - "traefik.http.routers.teleuploader.tls.certresolver=letsencrypt"
      - "traefik.http.services.teleuploader.loadbalancer.server.port=3000"

networks:
  app-shared-net:
    name: app-shared-net
    external: true
  • Step 2: Commit docker-compose configuration Run:
git add docker-compose.yml
git commit -m "chore: add docker-compose.yml with Traefik configurations"

Task 3: GitHub Actions Deployment Pipeline

Files:

  • Create: .github/workflows/deploy.yml

  • Step 1: Create GitHub Action deploy workflow Write to /.github/workflows/deploy.yml:

name: Deploy TeleUploader

on:
  push:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          bun-version: 1.1

      - name: Install dependencies
        run: bun install --frozen-lockfile

      - name: Run Tests
        env:
          BOT_TOKEN: "mock_token"
          STORAGE_CHANNEL_ID: "123456"
          BASE_URL: "http://localhost:3000"
          DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/postgres"
          PORT: "3000"
        run: bun run test

  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and Push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/mytheclipse/teleuploader:latest

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to VPS via SSH
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: 45.127.35.244
          username: root
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            mkdir -p /opt/teleuploader
            cd /opt/teleuploader
            
            # Log in to GHCR on VPS
            echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
            
            # Write dynamic docker-compose.yml
            cat << 'EOF' > docker-compose.yml
            version: '3.8'

            services:
              app:
                image: ghcr.io/mytheclipse/teleuploader:latest
                container_name: teleuploader-app
                restart: always
                environment:
                  - BOT_TOKEN=${BOT_TOKEN}
                  - STORAGE_CHANNEL_ID=${STORAGE_CHANNEL_ID}
                  - BASE_URL=${BASE_URL}
                  - DATABASE_URL=${DATABASE_URL}
                  - PORT=3000
                  - NODE_ENV=production
                  - LOG_LEVEL=info
                networks:
                  - app-shared-net
                labels:
                  - "traefik.enable=true"
                  - "traefik.http.routers.teleuploader.rule=Host(`upload.asepharyana.tech`)"
                  - "traefik.http.routers.teleuploader.entrypoints=websecure"
                  - "traefik.http.routers.teleuploader.tls=true"
                  - "traefik.http.routers.teleuploader.tls.certresolver=letsencrypt"
                  - "traefik.http.services.teleuploader.loadbalancer.server.port=3000"

            networks:
              app-shared-net:
                name: app-shared-net
                external: true
            EOF

            # Write .env file from secrets
            cat << EOF > .env
            BOT_TOKEN=${{ secrets.BOT_TOKEN }}
            STORAGE_CHANNEL_ID=${{ secrets.STORAGE_CHANNEL_ID }}
            BASE_URL=${{ secrets.BASE_URL }}
            DATABASE_URL=${{ secrets.DATABASE_URL }}
            PORT=3000
            EOF

            # Pull latest docker image
            docker compose pull

            # Run DB migrations
            docker compose run --rm app bun run db:migrate

            # Start service
            docker compose up -d
  • Step 2: Commit workflow Run:
git add .github/workflows/deploy.yml
git commit -m "ci: add GitHub Actions pipeline for tests, Docker build, and deployment"

Task 4: GitHub Secrets Configuration

Files:

  • Run Commands with gh CLI locally

  • Step 1: Check GitHub CLI authentication Run: gh auth status Expected: Authenticated successfully as MythEclipse (or similar). If not authenticated, prompt user to login using ! gh auth login.

  • Step 2: Configure SSH_PRIVATE_KEY secret Run: gh secret set SSH_PRIVATE_KEY < ~/.ssh/id_rsa (adjust path if custom key is used). Expected: Secret successfully set.

  • Step 3: Configure environment secrets Run:

gh secret set BOT_TOKEN --body "8605908810:AAFpUzlIBktfd_7wpEj7zMJob2CFxvG-ZGY"
gh secret set STORAGE_CHANNEL_ID --body "-1003996572954"
gh secret set BASE_URL --body "https://upload.asepharyana.tech"
gh secret set DATABASE_URL --body "postgresql://neondb_owner:npg_2ziHMPwZCet9@ep-long-glitter-ao3sjoyu-pooler.c-2.ap-southeast-1.aws.neon.tech/dcbot?sslmode=verify-full&channel_binding=require&connect_timeout=10"

Expected: All secrets successfully set in repository.