Guide
How to deploy a Solana program
On Solana, executable logic lives in programs — the chain's equivalent of smart contracts. A token swap, an NFT mint, a game settlement rule, or a payment verifier is all compiled Rust (or C) into BPF bytecode, uploaded to an on-chain account, and invoked by transactions. Deploying that bytecode is a distinct step from writing it: you need a funded wallet, a reliable RPC endpoint, a program keypair, and clarity about who can upgrade the code later. This guide walks through the full path from devnet practice to a cautious mainnet launch.
What you are deploying
A Solana program is not stored inside a user's wallet. It is a special
executable account owned by the BPF Loader — a built-in program that
knows how to run your bytecode when other transactions call
invoke on your program ID. Users and your frontend never send raw bytecode;
they send instructions (function selectors plus serialized arguments) to your program's
public key.
Three ideas matter before you run solana program deploy:
- Program ID — the base58 address derived from a keypair you generate or reuse. Clients hard-code or configure this address.
- Program account — holds the bytecode and metadata; must stay rent-exempt (a one-time SOL deposit based on size).
- Upgrade authority — the key that can replace bytecode later. Lose it and the program is frozen forever (or until you planned a buffer migration).
Most tutorials deploy to devnet first — free test SOL, same loader semantics, no financial risk. See our devnet vs mainnet guide if you are unsure which cluster your wallet points at.
Prerequisites
Before deploying, assemble the toolchain and a funded deployer wallet:
- Rust + Solana toolchain — install via
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"(or the current Anza release channel). Verify withsolana --version. - SBF target —
cargo build-sbfcompiles your program for Solana's runtime. Anchor wraps this for you. - Deployer keypair — default at
~/.config/solana/id.json. Fund it with devnet SOL (solana airdrop 2) or mainnet SOL from an exchange. - RPC URL — set with
solana config set --url devnetor a dedicated provider URL. Public endpoints work for learning; production deploys benefit from a private node — see RPC endpoints explained.
Check your config: solana config get should show the cluster, RPC URL, and
keypair path you expect. A wrong cluster is the fastest way to burn real SOL on a test
deploy or wonder why devnet faucets fail on mainnet.
Generate and secure your program keypair
Each program needs its own keypair. The public key becomes the program ID clients call.
solana-keygen new -o target/deploy/my_program-keypair.json
Back up this file. If you deploy without specifying a keypair, the CLI
generates one — but Anchor projects usually commit the path in Anchor.toml
under [programs.devnet] or [programs.mainnet]. For mainnet,
store keypairs offline or in a secrets manager; anyone with the file can impersonate
upgrade authority if you assign it to that key.
Anchor can sync the declared program ID into your source:
anchor keys sync updates declare_id! macros to match the
keypair on disk.
Deploy with the Solana CLI (native Rust)
After cargo build-sbf produces
target/deploy/my_program.so, upload it:
solana program deploy \
target/deploy/my_program.so \
--program-id target/deploy/my_program-keypair.json \
--url devnet
The CLI chunks the binary, creates or resizes the program account, and sets the upgrade
authority to your default keypair unless you pass --upgrade-authority. On
success you get a transaction signature and the program ID printed to stdout.
Verify on-chain
solana program show <PROGRAM_ID>— authority, data length, last deploy slot.- Block explorer — paste the program ID into Solscan or Solana FM; confirm "Executable" and owner BPF Loader Upgradeable.
- Invoke a smoke-test instruction from your client or integration test.
Our Solscan walkthrough helps decode the deploy transaction if something looks off.
Deploy with Anchor
Anchor is the dominant framework for Solana programs: account validation macros, IDL generation, and integrated deploy scripts. Typical workflow:
anchor build— compiles and emitstarget/deploy/<name>.soplus IDL JSON.- Configure
Anchor.toml— cluster URL, wallet path, program IDs per cluster. anchor deploy --provider.cluster devnet— uploads bytecode and updates on-chain program account.anchor test— spins up a local validator or hits devnet for integration tests before you ship.
Anchor's deploy command is a wrapper around the same BPF loader mechanics
as the raw CLI. The value is consistency: your tests, IDL, and TypeScript client all
reference the same program name. For a frontend-only product that never ships custom
bytecode — e.g. accepting SOL via Solana Pay — you may not need Anchor at all; see
accept Solana payments
for a server-verified flow without deploying programs.
Upgrade authority and program upgrades
Solana's upgradeable loader lets you patch bugs after launch. The wallet listed as upgrade authority can:
- Deploy a new buffer account with revised bytecode.
- Point the program account at the new buffer (
solana program deploywith the same program ID). - Optionally transfer or revoke authority (
solana program set-upgrade-authority).
Revoking upgrade authority makes the program immutable — required for some audits and trust models (users know the rules cannot change). Do this only when you are certain: there is no undo without deploying an entirely new program ID and migrating user state.
Multisig upgrade authorities (e.g. Squads) are common for teams: the program ID stays fixed while three-of-five signers must approve each upgrade.
Rent, size, and compute limits
Program accounts pay rent exemption upfront — SOL locked proportional to bytecode size. Large programs cost more to deploy but do not incur ongoing rent while exempt. Deployment transactions also consume compute units; congested mainnet may need a priority fee so the deploy lands in the next few slots.
Solana imposes a max program size (on the order of hundreds of KB per
deploy chunking rules). If your binary is huge, split logic across multiple programs or
trim dependencies. Profile with ls -lh target/deploy/*.so before mainnet.
Devnet checklist
Run through this on devnet before touching mainnet:
| Step | Why it matters |
|---|---|
| Point RPC and wallet at devnet | Avoids accidental mainnet spend |
| Airdrop or faucet SOL to deployer | Deploy + account creation need fees |
Deploy + program show |
Confirms bytecode landed |
| Integration tests / manual invoke | Catches account constraint bugs early |
| Practice upgrade + rollback | Builds muscle memory before production |
| Document program ID + authority keys | On-call debugging without guesswork |
Moving to mainnet
Mainnet deployment is the same commands with a different cluster URL and real SOL. Extra precautions:
- Separate keys — do not reuse devnet deployer keys that lived on shared laptops.
- Dedicated RPC — deploy transactions are large; public rate limits cause flaky uploads.
- Verify build reproducibility — tag the git commit, archive the
.so, consider verified builds for community trust. - Monitor the deploy tx — use
payment verification patterns
(same RPC
getTransactionlogic) to confirm finalization before announcing the program ID. - Plan upgrade policy — document whether authority stays with a multisig, gets revoked, or timelocks.
Frontend and indexer teams need the mainnet program ID, IDL, and cluster commitment level
(confirmed vs finalized) before launch. Our
Garden Dice build log
shows how a production site wires RPC and verification without exposing keys — useful
context even if your program is unrelated to games.
Common deployment errors
- Insufficient funds — deployer needs SOL for fees plus rent exemption. Top up the wallet.
- Blockhash not found — retry with fresh blockhash; often RPC lag or an old cached transaction.
- Program account not rent exempt — usually handled by the CLI; manual account creation must use
solana rentto calculate lamports. - Upgrade authority mismatch — only the current authority can redeploy; check
program show. - 429 / rate limited RPC — switch provider or add retries with backoff.
- Declared ID mismatch (Anchor) — run
anchor keys syncso sourcedeclare_id!matches the keypair.