Skip to content

Docker Deploy Guide

This guide explains how to deploy OhShii Launcher canisters using Docker for reproducible, verifiable builds suitable for DAO governance.

  • Reproducible: Same source code → same WASM hash, regardless of who builds it
  • Verifiable: Community members can verify deployed WASMs match the source code
  • Transparent: No local environment dependencies that could differ between developers
  • Trustless: DAO voters can independently verify what they’re approving

This guide covers the full Docker deployment workflow, .dockerignore optimization, environment variables, and troubleshooting.


  1. Docker Desktop installed and running
  2. dfx installed and identity configured
  3. Git repository up to date
  4. expect command for automated password entry
    • Script will offer to install if missing: brew install expect
    • Required for docker-deploy.sh automated script
  5. didc (Candid compiler) - optional, script auto-downloads if missing
    • Used to generate TypeScript declarations for dynamic canisters
    • Auto-downloaded to /tmp/didc by docker-build.sh if not found

Single Command Build + Deploy (Backend + Frontend)

Section titled “Single Command Build + Deploy (Backend + Frontend)”
Terminal window
cd /path/to/dev-ohshii-launcher
./docker-deploy.sh --build

This single script handles everything:

  • ✓ Checks all prerequisites (Docker, dfx, Node.js, npm, expect)
  • ✓ Installs npm dependencies if needed
  • Builds backend WASMs with Docker (with --build flag)
  • ✓ Builds frontend with Vite
  • ✓ Deploys all 5 canisters (4 backend + 1 frontend) to IC mainnet
  • ✓ Verifies backend hashes automatically

That’s it! The script handles the complete deployment pipeline.


docker-deploy.sh - Full Deployment Pipeline

Section titled “docker-deploy.sh - Full Deployment Pipeline”

What it does:

  1. Checks prerequisites (Docker, dfx, Node.js, npm, expect)
  2. Optionally builds WASMs with Docker (if --build flag or WASMs missing)
  3. Builds frontend with npm run build
  4. Deploys all 4 backend canisters using Docker-built WASMs
  5. Deploys frontend canister
  6. Verifies hashes match expected values

Usage:

Terminal window
./Deploy/docker-deploy.sh # Use existing WASMs (skips Docker build if files exist)
./Deploy/docker-deploy.sh --build # Force Docker rebuild (use after code changes!)

The script switches to the project root automatically, so you can run it from the project root or from Deploy/. Backend deployment verification accepts both “Upgraded code” and “Upgrading code for canister” (IC output) as success.

Important: Always use --build after modifying Rust code!


What it does:

  1. Builds all Rust canisters inside Docker (reproducible)
  2. Optimizes WASMs with ic-wasm shrink
  3. Adds Candid metadata to WASMs
  4. Creates deterministic .wasm.gz files with gzip -9 -n (for DAO proposals)
  5. Generates TypeScript declarations for sons_governance with didc
  6. Creates WASM_HASHES.txt with SHA-256 hashes (both raw and gzipped)

Usage:

Terminal window
./docker-build.sh # Build WASMs only, no deploy

Output files in Deploy/wasm/:

ohshii_launcher_backend_docker.wasm # Raw WASM
ohshii_launcher_backend_docker.wasm.gz # Deterministic gzip (for DAO proposals)
dao_storage_docker.wasm
dao_storage_docker.wasm.gz
pool_manager_docker.wasm
pool_manager_docker.wasm.gz
ohshii_governance_docker.wasm
ohshii_governance_docker.wasm.gz
sons_governance_docker.wasm
sons_governance_docker.wasm.gz

Output files in Deploy/:

WASM_HASHES.txt # Contains both raw and .wasm.gz hashes

For DAO voters: The .wasm.gz hashes in WASM_HASHES.txt are what should be compared with the proposal’s WASM Hash or Content Hash.


What it does:

  • Deploys existing *_docker.wasm files to IC mainnet
  • Does NOT build (assumes WASMs already exist)
  • Useful for re-deploying without rebuilding

Usage:

Terminal window
./quick-deploy.sh # Deploy existing WASMs

Interface-safety gate (atomic storage ↔ backend wave)

Section titled “Interface-safety gate (atomic storage ↔ backend wave)”

docker-deploy.sh and quick-deploy.sh run a committed pre-flight gate — scripts/check_live_subtype.shbefore installing any canister. It is part of the repo (any authorized deployer who clones gets it; it is not a local-only helper).

What it protects. A change to an interface-sensitive dao_storage method (one that pairs atomically with ohshii_launcher_backend on the wire) is a hard atomic-deploy pair. Installing such a change non-atomically — backend without storage, or storage without backend — leaves the live pair on incompatible wires and traps the affected flows. The gate compares the methods you are about to ship against the live on-chain candid:service and:

  • passes if they match (no wire change), or
  • requires the wave to co-install dao_storage + ohshii_launcher_backend (+ pool_manager) and an explicit operator attestation that no ingress can hit a half-installed pair, otherwise it refuses the deploy (fail-closed).

Read identity. The live candid:service is public, read-only metadata, so the gate fetches it with the unencrypted default dfx identity — NOT your deploy identity (which is a password-protected PEM that can’t be decrypted non-interactively). This is a per-invocation override; your deploy still installs with the protected identity.

Environment variables.

VarEffect
OHSHII_WAVE_CONFIRMED=1Operator attests ingress is quiesced — required to ship a detected wire change in a co-membered wave.
OHSHII_READ_IDENTITY=<name>dfx identity used for the read-only metadata fetch (default: default).
OHSHII_SKIP_LIVE_SUBTYPE=1Emergency hotfix override — bypasses the gate, logs loudly. Use only with manual sign-off.
OHSHII_NETWORK=<net>Network for the metadata query (default: ic).

Exit codes: 0 safe to proceed · 1 unsafe wave (wire changed without co-membership / attestation) · 2 tooling error (cannot verify → fail-closed).

When you add a new storage↔backend wire, add its method name to SENSITIVE_METHODS in scripts/check_live_subtype.sh.


Terminal window
./docker-deploy.sh --build
Terminal window
# 1. Create password file (one-time)
cd Deploy
echo 'DFX_IDENTITY_PASSWORD="your-password"' > .env.deploy
# 2. Build and deploy
./docker-deploy.sh --build
Terminal window
./docker-build.sh
Terminal window
./docker-deploy.sh # Uses existing WASMs

If you want to build separately before deploying:

Terminal window
cd /path/to/dev-ohshii-launcher
Terminal window
./docker-build.sh

What this does:

  1. Builds sons_governance canister first (standalone)
  2. Optimizes with ic-wasm (shrink + Candid metadata)
  3. Copies governance WASM to src/backend/governance_wasm/ and gzips it
  4. Generates TypeScript/JavaScript declarations with didc for sons_governance
    • src/declarations/sons_governance/sons_governance.did.js
    • src/declarations/sons_governance/sons_governance.did.d.ts
    • Automatically downloads didc if not found
  5. Builds remaining canisters (ohshii_launcher_backend embeds the fresh governance WASM)
  6. Optimizes all output WASMs with ic-wasm
  7. Generates WASM_HASHES.txt with SHA-256 hashes

Build time: ~5-10 minutes (depending on hardware)

Output files in project root:

ohshii_launcher_backend_docker.wasm
dao_storage_docker.wasm
pool_manager_docker.wasm
ohshii_governance_docker.wasm
sons_governance_docker.wasm
WASM_HASHES.txt
Terminal window
cat WASM_HASHES.txt

Example output:

# WASM Hashes - Fri Feb 14 16:30:00 CET 2026
# Main canisters (deployed via docker-deploy.sh or dfx)
abc123...def ohshii_launcher_backend_docker.wasm
456789...abc dao_storage_docker.wasm
def012...345 pool_manager_docker.wasm
678901...bcd ohshii_governance_docker.wasm
# Dynamic canister (embedded in backend)
234567...890 sons_governance_docker.wasm
# Embedded governance WASM (gzipped, used by include_bytes! in backend)
890123...cde src/backend/governance_wasm/sons_governance.wasm.gz

Save this file - it’s your proof of what was built and deployed.


Section titled “Option A: Automated Script (Recommended - All-in-One)”

Uses docker-deploy.sh for automated build + deployment of ALL canisters (backend + frontend) with hash verification.

Create .env.deploy in Deploy directory:

Terminal window
cd Deploy
echo 'DFX_IDENTITY_PASSWORD="your-password-here"' > .env.deploy

Security: .env.deploy is already in .gitignore

Terminal window
./docker-deploy.sh

What it does:

  1. Checks prerequisites (Docker, dfx, Node.js, npm, expect)
    • Offers to install expect if missing
  2. Installs npm dependencies (if node_modules/ not present)
  3. Builds backend WASMs with Docker (if not present or use --build to force rebuild)
    • Generates WASM_HASHES.txt
  4. Builds frontend with Vite (npm run build)
    • Creates dist/ directory
  5. Deploys all 5 canisters to IC mainnet:
    • 4 backend canisters with Docker-built WASMs
    • 1 frontend canister with Vite-built assets
    • Automatically enters password using expect
    • Auto-confirms breaking changes with --yes flag
    • Verifies hash of each backend canister using dfx canister info
  6. Reports complete deployment summary

Options:

Terminal window
./docker-deploy.sh # Use existing WASMs or build if missing
./docker-deploy.sh --build # Force rebuild + deploy

Expected output:

========================================
OHSHII-LAUNCHER - Build & Deploy to IC
========================================
Checking prerequisites...
✓ Docker is running
✓ dfx is installed
✓ Node.js is installed (v20.11.0)
✓ npm is installed (10.2.4)
✓ expect is installed
✓ Identity password configured
✓ npm dependencies already installed
Using existing Docker-built backend WASMs (use --build to rebuild)
Building frontend with Vite...
✓ Frontend build completed
========================================
DEPLOYMENT CONFIRMATION
========================================
⚠️ WARNING: This will upgrade all canisters on IC mainnet.
Pre-deployment checklist:
✓ Backend Docker build completed
✓ Frontend build completed
✓ WASM hashes generated
✓ Identity configured
Canisters to deploy:
• ohshii_launcher_backend (backend)
• dao_storage (backend)
• pool_manager (backend)
• ohshii_governance (backend)
• ohshii_launcher_frontend (frontend)
Deploy to IC mainnet? (yes/no): yes
========================================
Starting deployment to IC mainnet...
========================================
1/4 Deploying ohshii_launcher_backend...
[OK] Hash verified: abc123...def
2/4 Deploying dao_storage...
[OK] Hash verified: 456789...abc
3/4 Deploying pool_manager...
[OK] Hash verified: def012...345
4/4 Deploying ohshii_governance...
[OK] Hash verified: 678901...bcd
========================================
Backend deployment summary:
✓ All backend canisters deployed and verified!
========================================
========================================
5/5 Deploying frontend...
========================================
✓ Frontend deployed successfully
========================================
DEPLOYMENT SUMMARY
========================================
✓ All canisters deployed successfully!
• Backend canisters: Verified ✓
• Frontend canister: Deployed ✓
========================================

Option B: Manual Deploy with Hash Verification

Section titled “Option B: Manual Deploy with Hash Verification”

For more control or if expect is not available.

Terminal window
# Token Launcher Backend
dfx canister install ohshii_launcher_backend \
--wasm ./ohshii_launcher_backend_docker.wasm \
--mode upgrade \
--network ic
# DAO Storage
dfx canister install dao_storage \
--wasm ./dao_storage_docker.wasm \
--mode upgrade \
--network ic
# Pool Manager
dfx canister install pool_manager \
--wasm ./pool_manager_docker.wasm \
--mode upgrade \
--network ic
# OHSHII Governance
dfx canister install ohshii_governance \
--wasm ./ohshii_governance_docker.wasm \
--mode upgrade \
--network ic

Note: sons_governance is NOT deployed directly - it’s embedded in ohshii_launcher_backend and deployed dynamically when LGEs are created.

After each deployment, verify the WASM hash:

Terminal window
# Get deployed canister info (includes module_hash)
dfx canister info ohshii_launcher_backend --network ic
# Compare with local WASM hash
shasum -a 256 ohshii_launcher_backend_docker.wasm

Or use the verification script:

Terminal window
./verify-canister-hash.sh ohshii_launcher_backend ohshii_launcher_backend_docker.wasm
./verify-canister-hash.sh dao_storage dao_storage_docker.wasm
./verify-canister-hash.sh pool_manager pool_manager_docker.wasm
./verify-canister-hash.sh ohshii_governance ohshii_governance_docker.wasm

Verify the new version is deployed:

Terminal window
dfx canister call ohshii_launcher_backend get_version --network ic
# Expected: ("1.0.0")
dfx canister call dao_storage get_version --network ic
# Expected: ("1.0.0")
dfx canister call pool_manager get_version --network ic
# Expected: ("1.0.0")
dfx canister call ohshii_governance get_version --network ic
# Expected: ("1.0.0")
Terminal window
dfx canister status ohshii_launcher_backend --network ic
dfx canister status dao_storage --network ic
dfx canister status pool_manager --network ic
dfx canister status ohshii_governance --network ic

Check:

  • Status: Running
  • Memory allocation: Not excessive
  • Module hash: Matches WASM_HASHES.txt
  • Controllers: Correct (should include NNS, ohshii_governance, and admin principals)
Terminal window
# Test a read-only query to ensure canister is responding
dfx canister call dao_storage get_campaigns_count --network ic

When creating an upgrade proposal, provide:

GitHub Commit: abc123def456... (full 40-char hash)
Repository: https://github.com/your-org/ohshii-launcher
Branch: main
docker build -t ohshii-builder .
# Extract WASMs as documented in DOCKER_DEPLOY_GUIDE.md
Paste the .wasm.gz hashes from WASM_HASHES.txt
(These are what voters will compare with their local build)

Voters can verify the proposal using the one-click helper script:

Terminal window
# 1. Clone repository at proposal's commit
git clone https://github.com/ohshii-labs/ohshii-launcher
cd ohshii-launcher
git checkout <git_commit_hash>
# 2. Run reproducible Docker build
./Deploy/docker-build.sh
# 3. Verify hash with one-click script
./Deploy/verify-proposal.sh <canister_name>

Or verify manually by comparing the .wasm.gz hash from Deploy/WASM_HASHES.txt with the proposal’s WASM Hash.

See VOTER_VERIFICATION_GUIDE.md for detailed voter verification instructions.


Any community member can verify the build:

Terminal window
# Clone repository
git clone https://github.com/ohshii-labs/ohshii-launcher
cd ohshii-launcher
# Checkout exact commit
git checkout abc123def456
# Build with Docker
./Deploy/docker-build.sh
# Verify hashes with the helper script
./Deploy/verify-proposal.sh --all
# Or compare hashes manually
cat Deploy/WASM_HASHES.txt
# The .wasm.gz hashes should be IDENTICAL to the proposal hashes

Important: The .wasm.gz files are created with gzip -9 -n which strips timestamps from the gzip header, ensuring identical hashes across different machines and build times.


Docker uses layer caching. Each RUN command creates a layer:

RUN rustup target add wasm32-unknown-unknown # Layer 1 (cached)
RUN cargo install ic-wasm # Layer 2 (cached)
COPY . . # Layer 3 (invalidated when code changes)
RUN cargo build ... # Layer 4 (rebuilds because Layer 3 changed)

When you change source code:

  • The COPY . . layer is invalidated
  • All subsequent layers must rebuild

However, if only source code changed and Docker layers 1-2 are cached:

  • rustup and ic-wasm install are SKIPPED (cached)
  • Only cargo build runs

The .dockerignore file excludes unnecessary files from Docker context:

ExcludedReasonSize Saved
target/Rust build artifacts~6GB
node_modules/JS dependencies~500MB
.dfx/DFX local state~100MB
.git/Git history~50MB

Before .dockerignore: ~7.4GB transferred (70+ seconds) After .dockerignore: ~100MB transferred (few seconds)

ScenarioTime
First build (no cache)~10-15 min
Code change (cache layers 1-2)~3-5 min
No code change (full cache)~30 sec

Solution:

Terminal window
# Start Docker Desktop, then retry
./docker-build.sh

Solution: Increase Docker memory allocation:

  1. Open Docker Desktop
  2. Settings → Resources → Memory
  3. Increase to at least 4GB
  4. Apply & Restart

Deploy Fails with “Module hash mismatch”

Section titled “Deploy Fails with “Module hash mismatch””

Cause: Trying to deploy a WASM that doesn’t match what dfx expects

Solution: Use --mode upgrade and ensure you’re deploying the Docker-built WASM:

Terminal window
dfx canister install <canister_name> \
--wasm ./<canister_name>_docker.wasm \
--mode upgrade \
--network ic

Cause: Deployed canister doesn’t match local WASM

Verification Logic:

  • “Module hash … is already installed” → Hash verified from output ✓
  • “Upgraded code for canister” → Deploy successful, hash = local WASM ✓
  • “error” / “failed” → Deploy failed ❌

Solution if verification fails:

  1. Verify you deployed the correct *_docker.wasm file
  2. Rebuild with ./docker-build.sh to ensure fresh build
  3. Redeploy with correct WASM file

Manual verification (if needed):

Terminal window
# Get deployed hash
dfx canister info <canister_name> --network ic | grep "Module hash"
# Get local WASM hash
shasum -a 256 <canister_name>_docker.wasm

Both should match (deployed hash has 0x prefix, local doesn’t).

What it means: dfx detected that the new Candid interface has breaking changes compared to the deployed version.

Common causes:

  • Added/removed public methods
  • Changed method signatures
  • Changed return types

Solution: The script automatically accepts breaking changes with --yes flag. To review changes manually, use:

Terminal window
dfx canister install <canister_name> --wasm <file>_docker.wasm --mode upgrade --network ic
# (without --yes flag to see warnings)

Problem: dfx deploy ohshii_launcher_frontend rebuilds all dependencies (backend canisters) with local cargo build, overwriting the Docker WASMs.

Cause: In dfx.json, frontend has dependencies: [backend canisters]. dfx deploy rebuilds them.

Solution: Use dfx canister install instead of dfx deploy for the frontend:

Terminal window
dfx canister install ohshii_launcher_frontend --mode upgrade --network ic

The docker-deploy.sh script handles this automatically.

Note: Gzip compression is only needed for sons_governance.wasm because it’s embedded in the backend using include_bytes!("sons_governance.wasm.gz").

For all other canisters (ohshii_launcher_backend, dao_storage, pool_manager, ohshii_governance), we deploy the raw WASM files directly. The hash shown in the dashboard is the hash of the raw WASM file.

”Password required” but expect not working

Section titled “”Password required” but expect not working”

Solution: Deploy manually without the script:

Terminal window
dfx canister install <canister_name> \
--wasm ./<canister_name>_docker.wasm \
--mode upgrade \
--network ic
# Enter password when prompted

The sons_governance canister is embedded in ohshii_launcher_backend:

src/backend/src/lib.rs
const GOVERNANCE_WASM: &[u8] = include_bytes!("../governance_wasm/sons_governance.wasm.gz");

Why?

  • Governance canisters are created dynamically per LGE campaign
  • Each LGE gets its own governance canister instance
  • The backend deploys governance canisters using the embedded WASM

TypeScript Declarations: Since sons_governance is NOT in dfx.json (it’s dynamic), the build script uses didc to generate TypeScript/JavaScript declarations:

Terminal window
# Automatically done by docker-build.sh:
didc bind src/sons_governance/sons_governance.did -t js > src/declarations/sons_governance/sons_governance.did.js
didc bind src/sons_governance/sons_governance.did -t ts > src/declarations/sons_governance/sons_governance.did.d.ts

Verification: The governance WASM hash is part of the backend’s WASM hash. When you verify the backend WASM hash, you’re also verifying the embedded governance WASM.

To update governance (and all canisters):

Terminal window
./docker-build.sh
# Then deploy with docker-deploy.sh or dfx deploy --network ic

  1. Update version constants in each modified canister’s lib.rs:

    const CANISTER_VERSION: &str = "1.0.0"; // Increment version
  2. Commit version changes:

    Terminal window
    git add .
    git commit -m "Bump version to 1.1.0"
    git push
  3. Tag the release:

    Terminal window
    git tag v1.1.0
    git push origin v1.1.0
  4. Build with Docker:

    Terminal window
    ./docker-build.sh
  5. Deploy and verify hashes

  6. Publish WASM_HASHES.txt to DAO:

    • Create GitHub release with tag
    • Attach WASM_HASHES.txt
    • Include commit hash and build instructions

  1. Always use Docker builds for production deployments

    • Never use local cargo build for mainnet
    • Never use dfx build for mainnet
  2. Verify hashes before and after deployment

    • Before: Check WASM_HASHES.txt is correct
    • After: Verify deployed hash matches local hash
  3. Keep .env.deploy secure

    • Never commit to git
    • Use strong passwords
    • Rotate passwords periodically
  4. Document every deployment

    • Git commit hash
    • WASM hashes
    • Deployment timestamp
    • Who deployed
  5. Test on testnet first (if available)

    • Deploy to testnet
    • Run integration tests
    • Only then deploy to mainnet

Terminal window
# 1. Navigate to project
cd /path/to/dev-ohshii-launcher
# 2. Build + Deploy with single command
./docker-deploy.sh
# Script automatically:
# - Checks prerequisites
# - Builds WASMs (if needed)
# - Deploys all canisters
# - Verifies hashes
# 3. Verify versions (post-deployment)
dfx canister call ohshii_launcher_backend get_version --network ic
dfx canister call dao_storage get_version --network ic
dfx canister call pool_manager get_version --network ic
dfx canister call ohshii_governance get_version --network ic
Terminal window
# Force rebuild even if WASMs exist
./docker-deploy.sh --build
# Or manually:
./docker-build.sh # 1. Build
./docker-deploy.sh # 2. Deploy

If deployment fails or issues are discovered:

Terminal window
# Get the previous WASM from backup or rebuild from previous commit
git checkout <previous-commit>
./docker-build.sh
# Redeploy previous version
./docker-deploy.sh

  • Dockerfile: Build configuration for reproducible builds
  • docker-build.sh: Automated build script (builds all canisters, produces .wasm and .wasm.gz)
  • docker-deploy.sh: Automated deployment script
  • verify-proposal.sh: One-click voter verification helper
  • VOTER_VERIFICATION_GUIDE.md: Detailed guide for voters verifying DAO proposals
  • WASM_HASHES.txt: Generated hash file (contains both raw and .wasm.gz hashes)
  • README.md: Full project documentation

Last Updated: February 2026
Canister Version: 1.0.0