Disclosure: I'm Antonin Courtadon, founder of ACLClouds.
This guide takes a minimal bot from your computer to a hosted process. You will write a bot that answers !ping with "pong" (discord.py or discord.js), upload it, start it and check the logs. Once that works, you can replace the code with your own.
You need:
- a Discord account and a server where you have administrator rights;
- Python 3.10+ or Node.js 18+ on your computer for a quick local test;
- a free bot slot on a hosting platform (we use ACLClouds in the examples, but the steps are similar elsewhere).
Step 1: Create the bot in the Discord Developer Portal
- Open the Discord Developer Portal and create a new application.
- Go to the Bot section and create the bot user.
- Click Reset Token and copy the token. Treat it like a password. Do not paste it into code, a repository, a screenshot or a chat.
- In the same section, enable Message Content Intent under Privileged Gateway Intents. Without it, your bot receives messages with an empty
contentfield, which breaks prefix commands. - Go to OAuth2 → URL Generator, select the
botscope and the permissions you need (at minimum Send Messages and Read Message History for this test). Open the generated URL and add the bot to your test server.
Step 2: Write the bot
discord.py (Python)
Create a folder with two files: bot.py and requirements.txt.
bot.py:
import os import discord intents = discord.Intents.default() intents.message_content = True client = discord.Client(intents=intents) @client.event async def on_ready(): print(f"Connected as {client.user}") @client.event async def on_message(message: discord.Message): if message.author.bot: return if message.content.strip() == "!ping": await message.channel.send("pong") client.run(os.environ["DISCORD_TOKEN"]) requirements.txt:
discord.py>=2.3,<3 discord.js (Node.js)
Create a folder with index.js and package.json.
index.js:
const { Client, GatewayIntentBits } = require('discord.js'); const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, ], }); client.once('ready', () => { console.log(`Connected as ${client.user.tag}`); }); client.on('messageCreate', (message) => { if (message.author.bot) return; if (message.content.trim() === '!ping') { message.reply('pong'); } }); client.login(process.env.DISCORD_TOKEN); package.json:
{ "name": "ping-bot", "version": "1.0.0", "main": "index.js", "scripts": { "start": "node index.js" }, "dependencies": { "discord.js": "^14.16.0" } } Two details matter in both versions. The token is read from an environment variable, never written in the file. And the message content intent is requested in the code and must also be enabled in the portal, or Discord refuses the connection with an error about privileged intents.
Step 3: Test on your computer
Before you upload anything, check that the bot connects. On Windows, Linux or macOS, the variable syntax differs slightly. On Linux or macOS:
export DISCORD_TOKEN="paste-your-token-here" python bot.py # discord.py # or npm install && npm start # discord.js When you see Connected as ... in the terminal, type !ping in the test server. If the bot answers "pong", the code works. Stop it with Ctrl+C.
If the bot does not answer, check these in order:
- The bot is in the server and has Send Messages in that channel.
- Message Content Intent is enabled in the portal.
- The token is current. If you reset it, the old one stops working.
Step 4: Upload the bot to the free host
Every hosting panel has its own screens, but the logic is the same: create a bot, upload the files, set the token, choose the start file, start the bot.
On ACLClouds, the flow looks like this:
- Create a bot from the dashboard. The free plan gives you one bot slot.
- Upload your files. Use the panel upload, or connect with SFTP. Each bot has its own SFTP access on port 2222, with a username of
bot-followed by 8 characters, shown on the bot page. - Paste the Discord token into the Discord token card. The panel refuses a value that looks like a variable name, which catches a common mistake: pasting the text
DISCORD_TOKENwhere the token itself should go. - Check the start file. The panel tries to detect
bot.pyorindex.jsautomatically. If it finds the wrong file or none, set it yourself. - Start the bot and open the logs tab.
Upload only your source and a dependency list. Do not upload node_modules, a virtual environment or your .env file. The free plan allows uploads up to 100 MB and 715 MB of storage, so smaller is safer.
Step 5: Read the logs
The first start is where most problems appear. What you should see:
-
Connected as YourBot#1234(or the new username format) in the logs; - no repeated error lines;
- the bot shows as online in your server.
Common errors and what they mean:
| Log message | Likely cause | Fix |
|---|---|---|
PrivilegedIntentsRequired / intents error | Message Content Intent is off in the portal | Enable it, then restart |
Improper token has been passed / LoginFailure | Token is wrong, reset, or has spaces | Copy the token again into the token card |
ModuleNotFoundError: No module named 'discord' | Dependencies not installed | Check requirements.txt is uploaded and installed by the start process |
Cannot find module 'discord.js' | node_modules missing | Check package.json is uploaded and dependencies install |
| Bot restarts in a loop | Exception at startup | Read the first traceback line, not the last one |
Step 6: Keep it running
Free slots expire. Note the date the bot was started and renew from the bot page within the window the platform allows (on ACLClouds, within 2 days before expiry, or once the bot is suspended). A bot that is not renewed stops, even if the code is correct.
Also keep your code outside the host. A Git repository, even private, lets you redeploy in a minute if something goes wrong.
Comparison: what to check on the host
| What to check | Why | ACLClouds free bot plan |
|---|---|---|
| Token handling | Pasting it in code leaks it | Discord token card per bot |
| Log access | You need the traceback | Logs and console per bot |
| File access | Quick fixes without redeploying | SFTP on port 2222 |
| Runtimes | Your library and its tools must be present | Node.js and Python images include ffmpeg, yt-dlp and deno |
| RAM | Bots grow in memory over time | 315 MB |
| Renewal | Expiry stops the bot | 4 days, renewable from the bot page |
Limits of this setup
- This is a minimal example. A real bot needs error handling, rate-limit awareness and a way to store data (a file or a database) that survives restarts.
- The free plan's 315 MB RAM is enough for the code above and for a modest command set. It is not a reason to cache everything.
- Do not run a bot on a host you do not trust with your token. If a host asks you to paste your token in a chat or an email, do not.
Frequently asked questions
Do I need to know Python or JavaScript to host a bot?
You need to be able to edit one file and read a traceback. The examples above are the whole program for a ping bot, so you can copy them and change the command.
Why does !ping not work when the bot is online?
Check Message Content Intent first. The bot can be online and still receive empty message text.
Should I use discord.py or discord.js?
Use the language you already know. Both are maintained and both work well on small hosts. Choose based on your team and the libraries you need, not on the hosting.
Where do I put the token?
In the token card on the bot page, not in your code. Your code reads it from the environment variable the platform provides, or from a local .env file that you never upload.
What if my bot needs ffmpeg or yt-dlp?
The ACLClouds Node.js and Python images include ffmpeg, yt-dlp and deno. Check the image you chose before you add a dependency that calls them.
Next steps
Once your ping bot answers, add one command at a time, and check RAM and logs after each change. The free bot plan page is at https://aclclouds.com/en/ and the French version at https://aclclouds.com/fr/.
Antonin Courtadon, founder of ACLClouds · https://aclclouds.com
