Skip to content

Docker Deployment Tutorial - Railway & PostgreSQL

Overview

This tutorial shows you how to deploy your Uniswap V2 adapter using Docker and Railway with a PostgreSQL database. We'll go through each step systematically.

Step 1: Create Dockerfile

Create a Dockerfile in your project root with this multi-stage build configuration:

FROM node:20-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
 
# Copy workspace files needed for dependency resolution
FROM base AS deps
WORKDIR /app
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY tsconfig.base.json ./
COPY packages/common/package.json ./packages/common/
COPY packages/common/tsconfig.json ./packages/common/
COPY projects/uniswapv3/package.json ./projects/uniswapv3/
COPY projects/uniswapv3/tsconfig.json ./projects/uniswapv3/
 
# Install all dependencies (including workspace deps)
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --no-frozen-lockfile
 
# Build stage - build common package and current project
FROM base AS build
WORKDIR /app
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY tsconfig.base.json ./
COPY packages/ ./packages/
COPY projects/uniswapv3/ ./projects/uniswapv3/
 
# Copy node_modules from deps stage
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/packages/common/node_modules ./packages/common/node_modules
COPY --from=deps /app/projects/uniswapv3/node_modules ./projects/uniswapv3/node_modules
 
WORKDIR /app/projects/uniswapv3
RUN pnpm squid-evm-typegen ./src/abi/ abi/*.json
 
# Build the common package first, then uniswapv3
WORKDIR /app
RUN pnpm --filter @absinthe/common build
RUN pnpm --filter @absinthe/uniswapv3 build
 
# Production stage
FROM base AS production
WORKDIR /app
 
# Copy only production dependencies
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/packages/common/node_modules ./packages/common/node_modules
COPY --from=deps /app/projects/uniswapv3/node_modules ./projects/uniswapv3/node_modules
 
# Copy built artifacts
COPY --from=build /app/packages/common/lib ./packages/common/lib
COPY --from=build /app/packages/common/package.json ./packages/common/
COPY --from=build /app/projects/uniswapv3/lib ./projects/uniswapv3/lib
COPY --from=build /app/projects/uniswapv3/package.json ./projects/uniswapv3/
 
# Copy source files needed for migrations
COPY --from=build /app/projects/uniswapv3/src ./projects/uniswapv3/src
COPY --from=build /app/projects/uniswapv3/tsconfig.json ./projects/uniswapv3/
 
WORKDIR /app/projects/uniswapv3
 
# Use exec form and proper exit handling
CMD ["node", "lib/main.js"]

Dockerfile Explanation

  • Multi-stage build: Optimizes image size by separating dependencies, build, and production
  • PNPM support: Uses PNPM for faster, more efficient package management
  • Workspace handling: Properly builds monorepo dependencies in correct order
  • Production optimization: Final image contains only necessary runtime files

Step 2: Push to Docker Registry

Build and push your Docker image to your preferred registry:

Option A: GitHub Container Registry (Recommended)

# Build the image
docker build -t ghcr.io/yourusername/your-adapter:latest .
 
# Login to GitHub Container Registry
echo $GITHUB_TOKEN | docker login ghcr.io -u yourusername --password-stdin
 
# Push the image
docker push ghcr.io/yourusername/your-adapter:latest

Option B: Docker Hub

# Build the image
docker build -t yourusername/your-adapter:latest .
 
# Login to Docker Hub
docker login
 
# Push the image
docker push yourusername/your-adapter:latest

Step 3: Make Docker Image Public

GitHub Container Registry

  1. Go to your GitHub repository
  2. Click the Packages tab
  3. Click on your package name
  4. Go to Package settings
  5. Scroll to "Danger Zone"
  6. Click "Change visibility"
  7. Select "Public"
  8. Confirm the change

Docker Hub

  1. Go to Docker Hub
  2. Navigate to your repository
  3. Click "Settings"
  4. Under "Visibility", select "Public"
  5. Save changes

Step 4: Use Absinthe Pre-built Image (Alternative)

For Absinthe team members, you can use our pre-built image:

ghcr.io/absinthelabs/absinthe-abs-app:latest

This image is already optimized and contains the latest adapter code.


Step 5: Deploy on Railway

5a: Create Empty Railway Project

  1. Go to Railway.app
  2. Click "New Project"
  3. Select "Empty Project"
  4. Give your project a name

5b: Add PostgreSQL Database

  1. In your Railway project dashboard
  2. Click "+ New"
  3. Select "Database"
  4. Choose "PostgreSQL"
  5. Click "Add PostgreSQL"

The database will be automatically provisioned with connection details.

5c: Add Docker Container

  1. Click "+ New" again
  2. Select "Deploy from Docker Image"
  3. Enter your Docker image URL:
    ghcr.io/yourusername/your-adapter:latest
    or
    ghcr.io/absinthelabs/absinthe-abs-app:latest
  4. Click "Deploy"

5d: Configure Environment Variables

  1. Click on your deployed service
  2. Go to "Variables" tab
  3. Add these environment variables:
RPC_URL_MAINNET=<your-ethereum-rpc-url>
RPC_URL_BASE=<your-base-rpc-url>
RPC_URL_HEMI=https://rpc.hemi.network/rpc
RPC_URL_POLYGON=<your-polygon-rpc-url>
 
ABS_CONFIG='{"balanceFlushIntervalHours":48,"dexProtocols":[{"type":"uniswap-v2","chainId":43111,"toBlock":0,"protocols":[{"name":"weth-usdc.e","contractAddress":"0xaf6ed58980b5a0732423469dd9f3f69d9dc6dab5","fromBlock":865348,"pricingStrategy":"coingecko","token0":{"coingeckoId":"weth","decimals":18},"token1":{"coingeckoId":"usd-coin","decimals":6},"preferredTokenCoingeckoId":"token1"},{"name":"hemibtc-usdc.e","contractAddress":"0x0621bae969de9c153835680f158f481424c0720a","fromBlock":1451314,"pricingStrategy":"coingecko","token0":{"coingeckoId":"bitcoin","decimals":8},"token1":{"coingeckoId":"usd-coin","decimals":6},"preferredTokenCoingeckoId":"token1"},{"name":"weth-hemibtc","contractAddress":"0x225283e4d46ab82561a285998517a9175f3e0f06","fromBlock":1435768,"pricingStrategy":"coingecko","token0":{"coingeckoId":"weth","decimals":18},"token1":{"coingeckoId":"hemibtc","decimals":8},"preferredTokenCoingeckoId":"token1"},{"name":"vusd-usdc.e","contractAddress":"0xf23eec60263de9b0c8472c58be10feba28d9eb53","fromBlock":1326101,"pricingStrategy":"coingecko","token0":{"coingeckoId":"vusd","decimals":18},"token1":{"coingeckoId":"usd-coin","decimals":6},"preferredTokenCoingeckoId":"token1"}]},{"type":"izumi","chainId":42161,"toBlock":0,"protocols":[{"name":"hemibtc-usdc.e","contractAddress":"0x0621bae969de9c153835680f158f481424c0720a","fromBlock":1451314,"pricingStrategy":"coingecko","token0":{"coingeckoId":"bitcoin","decimals":18},"token1":{"coingeckoId":"usd-coin","decimals":6},"preferredTokenCoingeckoId":"token1"},{"name":"vusd-weth","contractAddress":"0xa43fe16908251ee70ef74718545e4fe6c5ccec9f","fromBlock":1274620,"pricingStrategy":"coingecko","token0":{"coingeckoId":"vesper-vdollar","decimals":18},"token1":{"coingeckoId":"weth","decimals":18},"preferredTokenCoingeckoId":"token1"}]}],"txnTrackingProtocols":[{"type":"printr","name":"printr-base","contractAddress":"0xbdc9a5b600e9a10609b0613b860b660342a6d4c0","factoryAddress":"0x33128a8fc17869897dce68ed026d694621f6fdfd","chainId":8453,"toBlock":0,"fromBlock":30000000},{"type":"vusd-mint","name":"vusd-mint","contractAddress":"0xFd22Bcf90d63748288913336Cd38BBC0e681e298","chainId":1,"toBlock":0,"fromBlock":22017054},{"type":"demos","name":"demos","contractAddress":"0x70468f06cf32b776130e2da4c0d7dd08983282ec","chainId":43111,"toBlock":0,"fromBlock":1993447},{"type":"voucher","name":"voucher","contractAddress":"0xa26b04b41162b0d7c2e1e2f9a33b752e28304a49","chainId":1,"toBlock":0,"fromBlock":21557766}],"stakingProtocols":[{"type":"hemi","name":"hemi-staking","contractAddress":"0x4f5e928763cbfaf5ffd8907ebbb0dabd5f78ba83","chainId":43111,"toBlock":0,"fromBlock":1226394},{"type":"vusd-bridge","name":"vusd-bridge","contractAddress":"0x5eaa10F99e7e6D177eF9F74E519E319aa49f191e","chainId":1,"toBlock":0,"fromBlock":22695105}],"univ3Protocols":[{"type":"uniswap-v3","chainId":43111,"factoryAddress":"0xCdBCd51a5E8728E0AF4895ce5771b7d17fF71959","factoryDeployedAt":507517,"positionsAddress":"0xe43ca1Dee3F0fc1e2df73A0745674545F11A59F5","toBlock":0,"pools":[{"name":"weth-hemibtc","contractAddress":"0xd8a6e60661aa3e53bcd77cc0d20f462c2d9376b9","fromBlock":1370183},{"name":"weth-hemibtc","contractAddress":"0x3f1228f4bd8de23dec4e1fcf81f16ed7fde5d1a8","fromBlock":1335038},{"name":"weth-hemibtc","contractAddress":"0xe31377f1f1e200a24e8c2d5b059829635b8c34ea","fromBlock":1328544},{"name":"weth-hemibtc-3000","contractAddress":"0x51109fa53e904c17daeb320d56166900e23707d3","fromBlock":1271574},{"name":"weth-usdc.e-10000","contractAddress":"0xb46335caccccb3fdc2a3f40d9fd253f5f76919a0","fromBlock":1171886},{"name":"weth-usdc.e-500","contractAddress":"0x62a567bab98e96968e29b3815ea9b8c7bee56d6e","fromBlock":1164129},{"name":"weth-usdc.e-100","contractAddress":"0xc863ddd8ac2157f3a6fe85c9c383b98812cf1986","fromBlock":1283712},{"name":"weth-usdc.e-3000","contractAddress":"0x9580d4519c9f27642e21085e763e761a74ef3735","fromBlock":865336},{"name":"hemibtc-usdc.e","contractAddress":"0x57dac018e4c5f12dc917e4b4f77706f12fd1652b","fromBlock":1458961},{"name":"vusd-usdc.e","contractAddress":"0xda44564df732decfd93ba8539803c5a8df241954","fromBlock":1335361},{"name":"vusd-usdc.e-500","contractAddress":"0x3ecabba99bef583b58b8f0af6dd853cd9ed75ce3","fromBlock":1271042},{"name":"vusd-usdc.e-3000","contractAddress":"0x3e014a9ef5f45454d9bddb15741ce8a04e7d96f5","fromBlock":1342356},{"name":"vusd-usdc.e-10000","contractAddress":"0x8025ba536e4ed403acb87ec939d8b8b45527b3ce","fromBlock":1413533},{"name":"hemibtc-usdt-100","contractAddress":"0xd0c7fa41cd5079cf9c89f6cf85147b6499c05990","fromBlock":1420959},{"name":"hemibtc-usdt-500","contractAddress":"0x2450def5f751c569339d458384b80c2e96185404","fromBlock":1630644},{"name":"hemibtc-usdt-3000","contractAddress":"0x0c07240c009469cbf4e393484f692064dfef15ed","fromBlock":1518680},{"name":"usdc.e-usdt-3000","contractAddress":"0xc36a310bd93e2927452de563faaa89a86822a21f","fromBlock":1384088},{"name":"usdc.e-usdt-500","contractAddress":"0x6ab24c166b4e0a2f6edab3a65a7c2d9cd6dc7a06","fromBlock":1325792},{"name":"usdc.e-usdt-100","contractAddress":"0x94504f06f31bf4224eba82c58b280400b85e6df3","fromBlock":1341456},{"name":"msusd-vusd-500","contractAddress":"0x9a0943b78004530557dc3f4aabcc4a67ebc0fbfb","fromBlock":1336788},{"name":"weth-mseth-500","contractAddress":"0x805ac209d1f84fbc11dcdd3956040b1a1a2d4b44","fromBlock":1332745},{"name":"hemibtc-usdc.e-10000","contractAddress":"0x92787e904d925662272f3776b8a7f0b8f92f9bb5","fromBlock":1375049},{"name":"hemibtc-usdc.e-3000","contractAddress":"0xf5819f1b4db39f78c2ca1758a89c70cbbafe3bd8","fromBlock":1435746},{"name":"hemibtc-usdc.e-500","contractAddress":"0x343d7f2ec94d56f74a78a4a20e6de892785cc2bc","fromBlock":1421793}]}],"zebuProtocols":[{"type":"zebu","name":"zebu-new","toBlock":0,"clients":[{"name":"xyz-1","contractAddress":"0xD71954165a026924cA771C53164FB0a781c54C83","chainId":137,"fromBlock":61059459},{"name":"xyz-2","contractAddress":"0x3e4768dB375094b753929B7A540121d970fcb24e","chainId":137,"fromBlock":61059459},{"name":"xyz-3","contractAddress":"0x5859Ff44A3BDCD00c7047E68B94e93d34aF0fd71","chainId":8453,"fromBlock":15286409},{"name":"xyz-4","contractAddress":"0xE3EB2347bAE4E2C6905D7B832847E7848Ff6938c","chainId":137,"fromBlock":61695150},{"name":"xyz-5","contractAddress":"0x19633c8006236f6c016a34B9ca48e98AD10418B4","chainId":137,"fromBlock":64199277},{"name":"xyz-6","contractAddress":"0x0c18F35EcfF53b7c587bD754fc070b683cB9063B","chainId":8453,"fromBlock":20328800},{"name":"xyz-7","contractAddress":"0xDD4d9ae148b7c821b8157828806c78BD0FeCE8C4","chainId":137,"fromBlock":73490308}]},{"type":"zebu","name":"zebu-legacy","toBlock":0,"clients":[{"name":"xyz-1","contractAddress":"0xd7829F0EFC16086a91Cf211CFbb0E4Ef29D16BEE","chainId":8453,"fromBlock":27296063}]}]}'
ABSINTHE_API_URL=https://adapters.absinthe.network
ABSINTHE_API_KEY=<your-api-key>
COINGECKO_API_KEY=<your-coingecko-api-key>
 
REDIS_URL="redis://localhost:6379"
DB_URL=postgresql://postgres:postgres@localhost:5432/postgres

Refer .env.example for more details

How to Get These Values:

RPC URLs:
  • Go to Alchemy
  • Create a new app for your target network
  • Copy the HTTP URL
Absinthe API Key:
  • Contact the Absinthe team for API access
  • Get your API key and URL
CoinGecko API Key:

5e: Connect PostgreSQL and Container

  1. In your container service, go to "Settings" tab
  2. Scroll to "Service Connections" section
  3. Click "Connect" next to your PostgreSQL service
  4. Railway automatically sets the DATABASE_URL variable

5f: Check Deployment Logs

  1. Click on your container service
  2. Go to "Deployments" tab
  3. Click on the latest deployment
  4. Check the "Deploy Logs" and "Application Logs"

Expected Log Output:

 Container started successfully
 Database connection established
 Environment configuration loaded
 Starting Uniswap V2 processor...
 Processing batch with 10 blocks (18500000-18500010)