Block 23
How I deployed a dapp on Ropsten from an EC2 instance (without using Infura)
How I deployed a Truffle dApp to Ropsten from my own Parity node on AWS EC2 instead of Infura, and what changed since: Sepolia, geth plus a consensus client.
This is a fairly technical article that I want to publish to help fellow developers with an important part of dApp development: deployment!
This is not the usual spoon-feeding-style step-by-step programming/tech tutorial. I did include as much details and commands as I could, but depending on your skill level you might need to pause and do some extra googling.
I learned the Truffle Framework a few months ago and started playing around with it, then a month ago I decided to develop my first complete dApp, a decentralized file validator (more info on that on another article when I’ll finish it).
After getting to a fairly good working prototype I wanted to deploy it to the live internet, so I could reach it from any browser with MetaMask installed.
Everywhere I read on the web, people ALWAYS gave the same damn solution: use the infura.io service! Just paste this code here, put this API key there, and voila’, you are done! Your dapp is deployed! Easy!
The problem with this approach is that Infura charges a 50$ monthly subscription for having more than 3 projects and… I didn’t like that at all! the blockchain is distributed and public, therefore I should be able to deploy my damn dApp from my own device without paying a recurring subscription no?!
So I did pretend that infura didn’t exist at all, which means I had to figure out a way to run a full node. I chose to do this with an AWS EC2 instance because I did not want to run a full node on my laptop. Paying an EC2 instance just for a few hours (enough to sync the blockchain and deploy contracts) is way cheaper and less limiting than infura.
I was going to deploy to the Ropsten Testnet instead of mainnet for 3 reasons:
- The dApp wasn’t complete for public release yet, so no point in deploying it to mainnet and spend real ethers to do so.
- It was my first dApp deployment outside of the local development testrpc/ganache kind of blockchains, so I knew I would have fucked something up (and in fact I did!!).
- Again, no point in spending real ether for testing an incomplete dApp
I Created a t2.large instance and immediately installed node, npm, truffle and parity. Smaller EC2 instances can be probably used too, but haven’t tried so I cannot say for sure. Keep in mind that you can start and stop instances whenever you need them to save money.
I ran parity on Ropsten testnet with this command parity --chain ropsten --jsonrpc-apis web3,rpc,personal,parity_accounts,eth,net,parity,parity_set,signer which started the full node with an open RPC port on localhost on port 8545.
[!] Please note that the server ONLY accepted an ssh connections from my local IP and no others. Make sure the RPC port cannot be reached from the outside.
So I had a full node running on my EC2 instance while parity was running and syncing with the ropsten blockchain. How could I check the progress on syncing? I had to connect to a web3 console and ask my node.
I went back to my laptop just to add this to the truffle-config.js file:
...
ropsten: {
host: "127.0.0.1",
port: 8545,
network_id: 3,
gas: 4700000,
from: "0x8874d4a8296fc415d0d8289b6d0682c964b78668"
},
...
(Note that the value in the _from:_ field is the account that will deploy the contract, you might not have one now, so make sure you update it later.)
Then I pushed changes and did a git clone into the EC2 server to bring this repo into the cloud instance where parity is already running.
I entered the repository’s root directory (where the truffle-config.js file is) and executed this command in the terminal: truffle console --network ropsten.
This opened up a truffle console since my parity node was running fine the prompt should say: truffle(ropsten)>. (If the parity node was not running, I would have gotten an error)
I checked the progress on syncing with web3.eth.isSyncing(). Which returned something like this:
{
currentBlock: 0,
highestBlock: 6605903,
startingBlock: 0,
warpChunksAmount: '0xd0e',
warpChunksProcessed: '0x8c'
}
I knew when the syncing was over because false was the output of the web3.eth.isSyncing() command, which indicates that the ethereum node is synced, and therefore ready to deployed contracts.
When the node was synced and ready I created an account with web3.eth.personal.newAccount('write_password_here') and proceeded with funding that account with testnet ether (this account is the one that goes into the _from:_ field of the truffle-config js file, in the ropsten network section we created earlier). I used this faucet to fund the account: https://faucet.ropsten.be
To check an account balance use this command in the console: web3.eth.getBalance('0x123_YOUR_ADDRESS_HERE'). It doesn’t return ethers but wei, so make sure you do the conversion.
Last thing that the account needs before being able to deploy is to be unlocked. To unlock the account I used truffle web3.eth.personal.unlockAccount( accounts[0], 'your_account_password_here').
The account was finally loaded with some testnet ethers and unlocked, therefore ready to deploy contracts.
Still in the console, I ran compile to compile the solidity contracts and then for some reason migrate wound not hang at some point, so I had to execute migrate -f 2 instead, where 2 is the number of my migration. To find the migration number I looked at the name of the migration js file I wanted to use, mine was migrations/2_deploy_contracts.js so I put 2 in the migrate command.
Everything went well and I had an output like this one:
$ truffle(ropsten)> migrate -f 2
Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
Migrations dry-run (simulation)
===============================
> Network name: 'ropsten-fork'
> Network id: 3
> Block gas limit: 0x7a121d
2_deploy_contracts.js
=====================
Deploying 'Authenticity'
------------------------
> block number: 6604876
> block timestamp: 1571514628
> account: 0x8874d4a8296FC415D0D8289B6d0682C964b78668
> balance: 1.586563006
> gas used: 553847
> gas price: 2 gwei
> value sent: 0 ETH
> total cost: 0.001107694 ETH
-------------------------------------
> Total cost: 0.001107694 ETH
Summary
=======
> Total deployments: 1
> Final cost: 0.001107694 ETH
Starting migrations...
======================
> Network name: 'ropsten'
> Network id: 3
> Block gas limit: 0x7a121d
2_deploy_contracts.js
=====================
Deploying 'Authenticity'
------------------------
> transaction hash: 0x5d6b6471922c8d2161f14dd65d5119e2ba580475e39d8392525389a10cef0eac
> Blocks: 0 Seconds: 48
> contract address: 0x03a54C1903827Bac1b3A3377928912d7E4f413aA
> block number: 6604875
> block timestamp: 1571514624
> account: 0x8874d4a8296FC415D0D8289B6d0682C964b78668
> balance: 1.57849488
> gas used: 458791
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00917582 ETH
> Saving artifacts
-------------------------------------
> Total cost: 0.00917582 ETH
Summary
=======
> Total deployments: 1
> Final cost: 0.00917582 ETH
I waited for a bit and checked on etherscan if the contracts were deployed successfully. This is one of them https://ropsten.etherscan.io/address/0x03a54c1903827bac1b3a3377928912d7e4f413aa#code
At this point the contract (the app’s backend) was deployed and ready for interactions. I only had the React frontend left to deploy then the app would be fully live.
I did a git status on the server, and committed the files that changed inside client/src/ during the compilation and migration of the contracts. These files are important because they will allow the web3 library inside the frontend’s browser to “connect” to the smart contracts we just deployed and call their functions, etc.
I went into the frontend directory client/, installed the dependencies with npm install and finally ran npm run build which outputs a static deployment-ready version of our React frontend in the build/ folder.
I deployed the React frontend with Amazon S3 but any CDN is fine, I picked a CDN because they are generally safer than a server.
Visited the page with MetaMask connected to Ropsten, and the app loaded perfectly fine and looked just like when it was on the local truffle develop testnet.
Mission complete!
Deploy on mainnet
I haven’t deployed the dapp on the mainnet yet, I still have a few more features to implement… I bet that it must be a similar procedure as deploying on testnet, but with these key differences:
- Parity needs to run a mainnet node so do not include
--chain ropstenin the initial command. - The
truffle-config.jsfile needs to have a network config for mainnet. - Real ether need to be sent to the account that will deploy the contracts
Thank you for reading this! Hopefully it helped you deploy your amazing dApp project without too many headaches. Keep in mind that ethereum changes often so some commands/concepts in this article might not work in a year or two.. In these cases leave a comment!
You can leave a comment also if you have any doubts or questions, or want to add something to this topic. Did you ever deploy a dApp? Did you use the same method or a different one?
Update, 2026
Most of the tools in this walkthrough have been retired, so read it as a record of how it worked in 2019, not as instructions. What changed:
- Ropsten is shut down. It was deprecated after the Merge and shut down at the end of 2022, so the faucet and the etherscan link above no longer work. For deploying and testing applications, use Sepolia (chain ID 11155111).
- Parity is gone. Parity Ethereum became OpenEthereum, and OpenEthereum was discontinued in 2022. The
paritycommand above won’t run. Current execution clients include geth, Nethermind, Besu, Erigon and Reth. - An execution client is no longer a node on its own. Since the Merge in September 2022, Ethereum runs on proof of stake, and an execution client such as geth needs a consensus client (for example Lighthouse, Prysm, Teku, Nimbus or Lodestar) running next to it. The two talk over the authenticated Engine API, using a shared JWT secret. Follow each client’s own documentation to set the pair up. Expect syncing to need much more disk space and time than it did in 2019.
- Don’t keep unlocked accounts in the node. Creating and unlocking an account inside the node, as I did with
web3.eth.personal, was always risky, and geth has deprecated thepersonalAPI. Sign transactions in your deploy tool or with a hardware wallet, and send only the signed transaction to the node. - Truffle and Ganache have been sunset by ConsenSys (2023). Hardhat and Foundry are the common replacements, and both can deploy through any RPC endpoint, including your own node.
The main point of the post still holds: you don’t need a paid provider to deploy a contract. Run your own node, keep its RPC port reachable only from localhost, and point your deploy tool at it.