Hosting: Fly
This guide tells you about the ways you can host your grammY bots on Fly, either using Deno or Node.js.
Preparing Your Code
You can run your bot using both webhooks or long polling.
Webhooks
Remember that you should not call
bot
in your code when using webhooks..start()
- Make sure that you have a file which exports your
Bot
object, so that you can import it later to run it. - Create a file named
app
or.ts app
, or actually any name you like (but you should be remembering and using this as the main file to deploy), with the following content:.js
import { webhookCallback } from "https://deno.land/x/grammy@v1.32.0/mod.ts";
// You might modify this to the correct way to import your `Bot` object.
import { bot } from "./bot.ts";
const port = 8000;
const handleUpdate = webhookCallback(bot, "std/http");
Deno.serve({ port }, async (req) => {
const url = new URL(req.url);
if (req.method === "POST" && url.pathname.slice(1) === bot.token) {
try {
return await handleUpdate(req);
} catch (err) {
console.error(err);
}
}
return new Response();
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import express from "express";
import { webhookCallback } from "grammy";
// You might modify this to the correct way to import your `Bot` object.
import { bot } from "./bot";
const port = 8000;
const app = express();
app.use(express.json());
app.use(`/${bot.token}`, webhookCallback(bot, "express"));
app.use((_req, res) => res.status(200).send());
app.listen(port, () => console.log(`listening on port ${port}`));
2
3
4
5
6
7
8
9
10
11
12
13
We advise you to have your handler on some secret path rather than the root (/
). As shown in the highlighted line above, we are using the bot token (/<bot token>
) as the secret path.
Long Polling
Create a file named app
or app
, or actually any name you like (but you should be remembering and using this as the main file to deploy), with the following content:
import { Bot } from "https://deno.land/x/grammy@v1.32.0/mod.ts";
const token = Deno.env.get("BOT_TOKEN");
if (!token) throw new Error("BOT_TOKEN is unset");
const bot = new Bot(token);
bot.command(
"start",
(ctx) => ctx.reply("I'm running on Fly using long polling!"),
);
Deno.addSignalListener("SIGINT", () => bot.stop());
Deno.addSignalListener("SIGTERM", () => bot.stop());
bot.start();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Bot } from "grammy";
const token = process.env.BOT_TOKEN;
if (!token) throw new Error("BOT_TOKEN is unset");
const bot = new Bot(token);
bot.command(
"start",
(ctx) => ctx.reply("I'm running on Fly using long polling!"),
);
process.once("SIGINT", () => bot.stop());
process.once("SIGTERM", () => bot.stop());
bot.start();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
As you can see in the highlighted line above, we take some sensitive values (your bot token) from environment variables. Fly allow us to store that secret by running this command:
flyctl secrets set BOT_TOKEN="AAAA:12345"
You can specify other secrets in the same way. For more information about this secrets, see https://
Deploying
Method 1: With flyctl
This is the easiest method to go with.
Run
flyctl launch
to generate aDockerfile
andfly
file for deployment. But DO NOT deploy..toml shflyctl launch
1logCreating app in /my/telegram/bot Scanning source code Detected a Deno app ? App Name (leave blank to use an auto-generated name): grammy Automatically selected personal organization: CatDestroyer ? Select region: ams (Amsterdam, Netherlands) Created app grammy in organization personal Wrote config file fly.toml ? Would you like to set up a Postgresql database now? No ? Would you like to deploy now? No Your app is ready. Deploy with `flyctl deploy`
1
2
3
4
5
6
7
8
9
10
11shflyctl launch
1logCreating app in /my/telegram/bot Scanning source code Detected a NodeJS app Using the following build configuration: Builder: heroku/buildpacks:20 ? App Name (leave blank to use an auto-generated name): grammy Automatically selected personal organization: CatDestroyer ? Select region: ams (Amsterdam, Netherlands) Created app grammy in organization personal Wrote config file fly.toml ? Would you like to set up a Postgresql database now? No ? Would you like to deploy now? No Your app is ready. Deploy with `flyctl deploy`
1
2
3
4
5
6
7
8
9
10
11
12
13Deno: Change the Deno version and remove
CMD
if exist within theDockerfile
file. For example, in this case, we updateDENO
to_VERSION 1
..25 .2 Node.js: To change the Node.js version, you need to insert a
"node"
property inside an"engines"
property insidepackage
. For instance, we update the Node.js version to.json 16
in the example below..14 .0 dockerfile# Dockerfile ARG DENO_VERSION=1.25.2 ARG BIN_IMAGE=denoland/deno:bin-${DENO_VERSION} FROM ${BIN_IMAGE} AS bin FROM frolvlad/alpine-glibc:alpine-3.13 RUN apk --no-cache add ca-certificates RUN addgroup --gid 1000 deno \ && adduser --uid 1000 --disabled-password deno --ingroup deno \ && mkdir /deno-dir/ \ && chown deno:deno /deno-dir/ ENV DENO_DIR /deno-dir/ ENV DENO_INSTALL_ROOT /usr/local ARG DENO_VERSION ENV DENO_VERSION=${DENO_VERSION} COPY --from=bin /deno /bin/deno WORKDIR /deno-dir COPY . . ENTRYPOINT ["/bin/deno"] # CMD is removed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26json// package.json { "name": "grammy", "version": "1.0.0", "description": "grammy", "main": "app.js", "author": "itsmeMario", "license": "MIT", "dependencies": { "express": "^4.18.1", "grammy": "^1.11.0" }, "devDependencies": { "@types/express": "^4.17.14", "@types/node": "^18.7.18", "typescript": "^4.8.3" }, "engines": { "node": "16.14.0" } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21Edit
app
inside thefly
file. The path.toml .
(or/app .ts .
for Node.js) in the example below refers to the main file directory. You might modify them to match with your project’s directory. If you are using webhooks, make sure the port is same as the one in your configuration (/app .js 8000
).toml# fly.toml app = "grammy" kill_signal = "SIGINT" kill_timeout = 5 [processes] app = "run --allow-net ./app.ts" [[services]] http_checks = [] internal_port = 8000 processes = ["app"] protocol = "tcp" script_checks = [] [services.concurrency] hard_limit = 25 soft_limit = 20 type = "connections" [[services.ports]] force_https = true handlers = ["http"] port = 80 [[services.ports]] handlers = ["tls", "http"] port = 443 [[services.tcp_checks]] grace_period = "1s" interval = "15s" restart_limit = 0 timeout = "2s"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33toml# fly.toml app = "grammy" kill_signal = "SIGINT" kill_timeout = 5 [processes] app = "run --allow-net ./app.ts" # Simply omitting the whole [[services]] section # since we are not listening to HTTP
1
2
3
4
5
6
7
8
9
10toml# fly.toml app = "grammy" kill_signal = "SIGINT" kill_timeout = 5 [processes] app = "node ./build/app.js" # Adjust the NODE_ENV environment variable to suppress the warning [build.args] NODE_ENV = "production" [build] builder = "heroku/buildpacks:20" [[services]] http_checks = [] internal_port = 8000 processes = ["app"] protocol = "tcp" script_checks = [] [services.concurrency] hard_limit = 25 soft_limit = 20 type = "connections" [[services.ports]] force_https = true handlers = ["http"] port = 80 [[services.ports]] handlers = ["tls", "http"] port = 443 [[services.tcp_checks]] grace_period = "1s" interval = "15s" restart_limit = 0 timeout = "2s"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40toml# fly.toml app = "grammy" kill_signal = "SIGINT" kill_timeout = 5 [processes] app = "node ./build/app.js" # Adjust the NODE_ENV environment variable to suppress the warning [build.args] NODE_ENV = "production" [build] builder = "heroku/buildpacks:20" # Simply omitting the whole of the [[services]] section since we are not listening to HTTP.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16Run
flyctl deploy
to deploy your code.
Method 2: With GitHub Actions
The main advantage of following method is that Fly will watch for changes in your repository which includes your bot code, and it will deploy new versions automatically. Visit https://
Get a Fly API token by running
flyctl auth token
.Create a repository on GitHub, it can be either private or public.
Go to Settings, choose Secrets and create a secret called
FLY
with the value of the token from step 2._API _TOKEN Create
.github
with these contents:/workflows /main .yml ymlname: Fly Deploy on: [push] env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} jobs: deploy: name: Deploy app runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: superfly/flyctl-actions/setup-flyctl@master - run: flyctl deploy --remote-only
1
2
3
4
5
6
7
8
9
10
11
12Follow steps 2 until 4 from Method 1 above. Remember to skip the last step (step 5) since we are not deploying the code directly.
Commit your changes and push them up to GitHub.
This is where the magic happens—the push will have triggered a deploy and from now on, whenever you push a change, the app will automatically be redeployed.
Setting the Webhook URL
If you are using webhooks, after getting your app running, you should configure your bot’s webhook settings to point to your app. To do that, send a request to
https://api.telegram.org/bot<token>/setWebhook?url=<url>
replacing <token>
with your bot token, and <url>
with the full URL of your app along with the path to the webhook handler.
Dockerfile Optimization
When our Dockerfile
is run, it copies everything from the directory over to the Docker image. For Node.js applications, some directories like node
are going to be rebuilt anyway so there’s no need to copy them. Create a .dockerignore
file and add node
to it to do this. You can also use .dockerignore
to not copy any other files which aren’t needed at runtime.