Why Docker for Solana dApps?
Most Solana tutorials end at anchor deploy and leave deployment as an exercise for the reader. In production you need reproducible builds, isolated environments, and automated pipelines. Docker solves all three.
This guide covers the full DevOps setup for a typical Solana full-stack app: Next.js frontend, NestJS backend, PostgreSQL database — all containerized and deployed via GitHub Actions.
Project Structure
my-solana-app/
├── frontend/ # Next.js app
│ ├── Dockerfile
│ └── ...
├── backend/ # NestJS API
│ ├── Dockerfile
│ └── ...
├── programs/ # Anchor programs
│ └── my_program/
├── docker-compose.yml
├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── deploy.yml
└── .env.example
1. Dockerize the Next.js Frontend
# frontend/Dockerfile
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ARG NEXT_PUBLIC_SOLANA_RPC_URL
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_SOLANA_RPC_URL=$NEXT_PUBLIC_SOLANA_RPC_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
Enable output: 'standalone' in your next.config.mjs to generate a self-contained server. This reduces the Docker image from ~1GB to ~150MB.
// next.config.mjs
export default {
output: "standalone",
};2. Dockerize the NestJS Backend
# backend/Dockerfile
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npx prisma generate
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
COPY --from=builder /app/prisma ./prisma
EXPOSE 3001
CMD ["sh", "-c", The CMD runs migrations before starting the server, ensuring the database schema is always up to date on deploy.
3. Docker Compose for Local Development
# docker-compose.yml
version: "3.9"
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: solana_app
POSTGRES_PASSWORD: localdev
POSTGRES_DB: solana_app
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
backend:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "3001:3001"
environment:
DATABASE_URL: postgresql://solana_app:localdev@postgres:5432/solana_app
SOLANA_RPC_URL
docker compose up --buildFor local Anchor development, keep programs outside Docker. Use anchor test directly on the host since Solana CLI and BPF toolchain are heavy and slow in containers.
4. GitHub Actions CI Pipeline
Run tests and lint on every push:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
frontend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: frontend/package-lock.json
5. Deploy Pipeline
Build Docker images, push to a registry, and deploy:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
matrix:
service: [frontend, backend]
steps:
- uses: actions/checkout@v4
- uses
Never commit secrets or RPC URLs to the repository. Use GitHub Secrets for all sensitive values and inject them as build args or environment variables.
6. Anchor Program Deployment in CI
For deploying Anchor programs (not just testing them):
anchor-deploy:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: nicedoc/setup-anchor@v1
with:
anchor-version: "0.30.1"
solana-cli-version: "1.18.17"
- name: Configure Solana
run: |
echo "${{ secrets.DEPLOYER_KEYPAIR }}" > deployer.json
solana config set --url ${{ secrets.SOLANA_RPC_URL }}
solana config set --keypair deployer.json
- name: Deploy program
working-directory: programs
run: anchor deploy --provider.cluster ${{ secrets.SOLANA_CLUSTER }}Environment Strategy
| Environment | RPC | Database | Anchor Cluster |
|---|---|---|---|
| Local | Devnet or localnet | Docker PostgreSQL | localnet |
| Staging | Devnet | Cloud PostgreSQL | Devnet |
| Production | Mainnet (Helius/QuickNode) | Cloud PostgreSQL | Mainnet-Beta |
Manage this with separate .env files or GitHub environment secrets:
# .env.production
SOLANA_RPC_URL=https://mainnet.helius-rpc.com/?api-key=YOUR_KEY
DATABASE_URL=postgresql://user:pass@db.host:5432/prodProduction Checklist
| Item | Status |
|---|---|
| Multi-stage Dockerfiles (small images) | Required |
| Health checks in Docker Compose | Required |
| GitHub Secrets for all credentials | Required |
| Database migrations in deploy command | Required |
| Separate CI and deploy workflows | Recommended |
| Image tagging with git SHA | Recommended |
| Rollback strategy (previous image tag) | Recommended |
| Monitoring and alerting | Recommended |
| SSL/TLS via reverse proxy (Nginx/Caddy) | Required |
| Rate limiting on public APIs | Required |
Wrapping Up
Docker and GitHub Actions transform a "works on my machine" Solana project into a repeatable, deployable product. The patterns here — multi-stage builds, compose for local dev, separate CI and deploy workflows, environment-based config — work for any full-stack Solana app regardless of scale.
For the NestJS backend architecture, see our NestJS + Solana guide. For the Anchor program side, check the Coinflip tutorial or the Liquidity Locker guide.