Back

[SOLVED] Function always returns "Execution timed out." on self-hosted Appwrite

  • 0
  • Web
Dave
24 Sep, 2023, 09:55

I've been trying to get the Appwrite API to work in a Function, but I just can't seem to get it.

The function runs fine when I use just the Stripe API code, but once I start adding the Appwrite API to save some user preferences, it returns Execution timed out..

What I checked:

  • I'm instantiating a new Appwrite client
  • I'm setting endPoint, setProject, setJWT to the Appwrite client
  • I'm creating the Appwrite Account client
  • The Function initially had Users execute access, but I tried Any, to no avail

When I log the client

TypeScript
{
  "client": {
    "endpoint": "https://backend.*****.com/v1",
    "headers": {
      "accept-encoding": "*",
      "content-type": "",
      "x-sdk-name": "Node.js",
      "x-sdk-platform": "server",
      "x-sdk-language": "nodejs",
      "x-sdk-version": "11.0.0",
      "X-Appwrite-Response-Format": "1.4.0",
      "x-appwrite-project": "******",
      "x-appwrite-jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1*************"
    },
    "selfSigned": false
  }
}

My /src/lib/appwrite.ts:

TypeScript
constructor(
  APPWRITE_ENDPOINT: string,
  APPWRITE_PROJECT_ID: string,
  APPWRITE_USER_JWT: string,
) {
  this.client = new Client();
  this.account = new Account(this.client);

  this.client
    .setEndpoint(APPWRITE_ENDPOINT).   // These are logging fine
    .setProject(APPWRITE_PROJECT_ID)   // These are logging fine 
    .setJWT(APPWRITE_USER_JWT);        // These are logging fine
}

async getPreferences(
  functionContext: AppwriteFunctionContext,
): Promise<Record<string, string>> {
  try {
    return await this.account.getPrefs();
  } catch (err) {
    functionContext.error(err);        // It never seems to catch
  }

  return {};
}

My main.ts:

TypeScript
const userPreferences = await appwriteService.getPreferences(functionContext);

Any hint on what I'm doing wrong would be appreciated.

TL;DR
null
Dave
24 Sep, 2023, 11:44

Some findings:

  • I'm using a custom domain. When I set the endpoint in the function to the non-custom domain (e.g. https://appwrite.xxxxxx.com, it's contining the function instead of just returning nothing
  • In above code I'm using the Account SDK with a JWT, I've switched to the Users SDK with a global API key

More notes:

  • Appwrite 1.4.3
  • Self hosted on Digital Ocean
  • I'm triggering this function from a Next.js frontend with createExecution
  • The API key that I'm using never shows that it's accessed in the Appwrite dashboard, which is weird, because in the Function I initialise specifically with that key
Dave
24 Sep, 2023, 22:01
TypeScript
Error: connect ECONNREFUSED 127.0.1.1:443
    at AppwriteService.getPreferences (file:///usr/local/server/src/function/dist/lib/appwrite.js:22:19)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Module.default (file:///usr/local/server/src/function/dist/main.js:28:29)
    at async execute (/usr/local/server/src/server.js:141:22)
    at async /usr/local/server/src/server.js:158:13

When I get ECONNREFUSED, is that a server config error, or does it mean my credentials are invalid?

safwan
25 Sep, 2023, 05:00

connect ECONNREFUSED 127.0.1.1:443 I faced this exact issue, when I setup Appwrite on something other than 443.

Since you're self-hosting, can you tell me what your traefik is set up like? Specifically the traefik service:

TypeScript
services:
  traefik:
    image: traefik:2.7
    container_name: appwrite-traefik
    <<: *x-logging
    command:
      - --providers.file.directory=/storage/config
      - --providers.file.watch=true
      - --providers.docker=true
      - --providers.docker.exposedByDefault=false
      - --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
      - --entrypoints.appwrite_web.address=:80
      - --entrypoints.appwrite_websecure.address=:443
    restart: unless-stopped
    ports:
      - 5080:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - appwrite-config:/storage/config:ro
      - appwrite-certificates:/storage/certificates:ro
    depends_on:
      - appwrite
    networks:
      - gateway
      - appwrite

As you can see, traefik is set up to map 443 to 443.

If it helps, I am using Nginx Proxy Manager in front of Appwrite as a load balancer.

Drake
25 Sep, 2023, 05:03

Do you know why the IP is 127.0.1.1?

safwan
25 Sep, 2023, 05:05

ooh yeah that's weird

Dave
25 Sep, 2023, 06:11

@safwan I came across the question you posted before, but figured it must be something else since I'm using the default ports.

This is what my Traefik service looks like:

TypeScript
traefik:
  image: traefik:2.9
  container_name: appwrite-traefik
  <<: *x-logging
  command:
    - --providers.file.directory=/storage/config
    - --providers.file.watch=true
    - --providers.docker=true
    - --providers.docker.exposedByDefault=false
    - --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
    - --entrypoints.appwrite_web.address=:80
    - --entrypoints.appwrite_websecure.address=:443
  restart: unless-stopped
  ports:
    - 80:80
    - 443:443
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock
    - appwrite-config:/storage/config:ro
    - appwrite-certificates:/storage/certificates:ro
  depends_on:
    - appwrite
  networks:
    - gateway
    - appwrite
Dave
25 Sep, 2023, 06:26

Maybe a bit more information regarding my setup will help.

@Steven I have no idea why the IP is 127.0.1.1. Can I debug or log something that gives an answer to that question?

My setup:

  • I'm running Appwrite on a subdomain: https://appwrite.xxxxx.com
  • I've added a custom domain for the web to interact with: https://backend.xxxxx.com
  • The Function variables:
    • setEndpoint: https://appwrite.xxxxx.com/v1 (I tried the custom domain https://backend.xxxxx.com too)
    • setProject: xxxxx (confirmed in Appwrite)
    • setKey: xxxxx (new generated API key with all access)
  • The Function is deployed manually with Appwrite CLI. Build etc. is fine:
    • execute: users
    • I've rewrote it to just use updatePrefs and then getPrefs and then log it in the console.

This is the updatePreferences function:

TypeScript
async updatePreferences(
  functionContext: AppwriteFunctionContext,
  APPWRITE_USER_ID: string,
  userPreferences: Record<string, string>,
) {
  try {
    functionContext.log("updatePreferences");
    functionContext.log(userPreferences);
    functionContext.log(`APPWRITE_USER_ID: ${APPWRITE_USER_ID}`);
    return await this.users.updatePrefs(APPWRITE_USER_ID, userPreferences);
  } catch (err: any) {
    functionContext.log("updatePreferences error");
    functionContext.log(err.message);
    throw new Error(err.message);
  }
}

I call it like so:

TypeScript
const updatedUserPreferences = await appwriteService.updatePreferences(
  functionContext,
  APPWRITE_USER_ID,
  { TestKey: "TestValue" },
);

It always throws: connect ECONNREFUSED 127.0.1.1:443

Dave
25 Sep, 2023, 06:30

Relevant parts of my .env:

TypeScript
_APP_ENV=production
_APP_LOCALE=en
_APP_OPTIONS_ABUSE=enabled
_APP_OPTIONS_FORCE_HTTPS=enabled
_APP_OPENSSL_KEY_V1=xxxxx
_APP_DOMAIN=appwrite.xxxxx.com
_APP_DOMAIN_FUNCTIONS=
_APP_DOMAIN_TARGET=appwrite.xxxxx.com
_APP_INFLUXDB_HOST=influxdb
_APP_INFLUXDB_PORT=8086
_APP_STATSD_HOST=telegraf
_APP_STATSD_PORT=8125
_APP_SMTP_HOST=mailpit
_APP_SMTP_PORT=1025
_APP_FUNCTIONS_SIZE_LIMIT=30000000
_APP_FUNCTIONS_TIMEOUT=900
_APP_FUNCTIONS_BUILD_TIMEOUT=900
_APP_FUNCTIONS_CONTAINERS=10
_APP_FUNCTIONS_CPUS=0
_APP_FUNCTIONS_MEMORY=0
_APP_FUNCTIONS_MEMORY_SWAP=0
_APP_FUNCTIONS_RUNTIMES=node-20.0
_APP_EXECUTOR_SECRET=xxxxx
_APP_EXECUTOR_HOST=http://appwrite-executor/v1
_APP_FUNCTIONS_INACTIVE_THRESHOLD=300
OPEN_RUNTIMES_NETWORK=appwrite_runtimes
_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes
_APP_FUNCTIONS_MAINTENANCE_INTERVAL=3600
Dave
25 Sep, 2023, 06:50

Might've found something:

  • When I run docker exec appwrite vars, the _APP_FUNCTIONS_RUNTIMES_NETWORK value is empty
  • When I SSH to the server and check the .env file, the value is runtimes
  • When I run docker compose up -d, the env vars don't seem to reload / update

I guess my Appwrite install is corrupt. I installed 1.4.2 and followed the docs to update to 1.4.3, then ran the migration. I'll try a 1.4.3 clean install.

Dave
25 Sep, 2023, 07:15

Also strange:

cat .env | grep RUNTIME gives me:

TypeScript
_APP_EXECUTOR_RUNTIME_NETWORK="runtimes"
_APP_FUNCTIONS_RUNTIMES="node-20.0"
OPEN_RUNTIMES_NETWORK=appwrite_runtimes
_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes

docker exec appwrite vars | grep RUNTIME gives me:

TypeScript
- _APP_FUNCTIONS_RUNTIMES=node-20.0
- _APP_EXECUTOR_RUNTIME_NETWORK=
- OPEN_RUNTIMES_NETWORK=
- _APP_FUNCTIONS_RUNTIMES_NETWORK=

Is this expected or is there some sort of .env cache? This is after changing the .env file and running docker compose up -d.

Dave
25 Sep, 2023, 07:18

This might all go back to running the migration from 1.4.2 to 1.4.3. and the migration script breaking on the GitHub integration SSH key _APP_VCS_GITHUB_PRIVATE_KEY, because it's multiline.

I've manually updated the .env file after the migration from 1.4.2 to 1.4.3, but it seems something broke there.

Dave
25 Sep, 2023, 09:00

Allright. I must be doing something simple very wrong, it's still not working** after a fresh 1.4.3 install.**

I don't get connect ECONNREFUSED 127.0.1.1:443 anymore, but the function times seems to stop running and times out right after I call any of the Appwrite functions, specifically updatePrefs.

Any idea on how to debug this?

docker logs appwrite-worker-functions gives me:

TypeScript
Fetched 1 functions...
[Job] (65114aa9c68d01.13467678) successfully run.

docker logs appwrite-executor-********-65114879c1f66e6288ad gives me:

TypeScript
pm2 launched in no-daemon mode (you can add DEBUG="*" env variable to get more messages)
2023-09-25T08:45:53: PM2 log: Launching in no daemon mode
2023-09-25T08:45:54: PM2 log: [PM2] Starting /usr/local/server/src/server.js in fork_mode (1 instance)
2023-09-25T08:45:54: PM2 log: App [server:0] starting in -fork mode-
2023-09-25T08:45:54: PM2 log: App [server:0] online
2023-09-25T08:45:54: PM2 log: [PM2] Done.

docker logs appwrite gives me:

Nothing special.

cat .env | grep RUNTIME gives me:

TypeScript
_APP_FUNCTIONS_RUNTIMES=node-20.0
_APP_EXECUTOR_RUNTIME_NETWORK=appwrite_runtimes
OPEN_RUNTIMES_NETWORK=appwrite_runtimes
_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes

docker exec appwrite vars | grep RUNTIME gives me:

TypeScript
- _APP_FUNCTIONS_RUNTIMES=node-20.0
- _APP_EXECUTOR_RUNTIME_NETWORK=
- OPEN_RUNTIMES_NETWORK=
- _APP_FUNCTIONS_RUNTIMES_NETWORK=
Dave
25 Sep, 2023, 09:05

The function code (node-20):

TypeScript
import { getEnvironmentVariablesOrThrow, getHeadersOrThrow, } from "./utils/index.js";
import AppwriteService from "./lib/appwrite.js";

export default async (functionContext) => {
    const { req, res, log } = functionContext;
    
    const { APPWRITE_USER_ID } = getHeadersOrThrow(functionContext, req.headers, [
        "x-appwrite-user-id",
    ]);
    
    const { APPWRITE_ENDPOINT, APPWRITE_PROJECT_ID, APPWRITE_API_KEY } = getEnvironmentVariablesOrThrow(functionContext, process.env, [
        "APPWRITE_ENDPOINT",
        "APPWRITE_PROJECT_ID",
        "APPWRITE_API_KEY",
    ]);
    
    log(`APPWRITE_USER_ID: ${APPWRITE_USER_ID}`);
    log(`APPWRITE_ENDPOINT: ${APPWRITE_ENDPOINT}`);
    log(`APPWRITE_PROJECT_ID: ${APPWRITE_PROJECT_ID}`);
    log(`APPWRITE_API_KEY: ${APPWRITE_API_KEY}`);
    
    const appwriteService = new AppwriteService(APPWRITE_ENDPOINT, APPWRITE_PROJECT_ID, APPWRITE_API_KEY);
    
    const updatedUserPreferences = await appwriteService.updatePreferences(functionContext, APPWRITE_USER_ID, { TestKey: "TestValue" });
    log(updatedUserPreferences);
    
    const userPreferences = await appwriteService.getPreferences(functionContext, APPWRITE_USER_ID);
    log(userPreferences);

    return res.json({
        status: "success",
    }, 200);
};
Dave
25 Sep, 2023, 09:05

The appwrite service code in the function

TypeScript
import { Client, Users } from "node-appwrite";
class AppwriteService {
    client;
    users;
    constructor(APPWRITE_ENDPOINT, APPWRITE_PROJECT_ID, APPWRITE_API_KEY) {
        this.client = new Client();
        this.client
            .setEndpoint(APPWRITE_ENDPOINT)
            .setProject(APPWRITE_PROJECT_ID)
            .setKey(APPWRITE_API_KEY);
        this.users = new Users(this.client);
    }
    async getPreferences(functionContext, APPWRITE_USER_ID) {
        try {
            functionContext.log("getPreferences");
            functionContext.log(`APPWRITE_USER_ID: ${APPWRITE_USER_ID}`);
            return await this.users.getPrefs(APPWRITE_USER_ID);
        }
        catch (err) {
            functionContext.log("getPreferences error");
            functionContext.log(err.message);
            throw new Error(err.message);
        }
    }
    async updatePreferences(functionContext, APPWRITE_USER_ID, userPreferences) {
        try {
            functionContext.log("updatePreferences");
            functionContext.log(userPreferences);
            functionContext.log(`APPWRITE_USER_ID: ${APPWRITE_USER_ID}`);
            return await this.users.updatePrefs(APPWRITE_USER_ID, userPreferences);
        }
        catch (err) {
            functionContext.log("updatePreferences error");
            functionContext.log(err.message);
            throw new Error(err.message);
        }
    }
}
export default AppwriteService;
Drake
25 Sep, 2023, 21:39

what is APPWRITE_ENDPOINT now and where is your appwrite instance installed?

Dave
25 Sep, 2023, 23:53

APPWRITE_ENDPOINT is https://appwrite.xxxxx.com/v1

My Appwrite instance is installed on a Digital Ocean droplet and publicly accessible. Path is /root/appwrite. I can share the domain / IP if you'd like to.

I created a new droplet yesterday with a Docker image pre-installed (link), then ran the Unix install command for version 1.4.3, found in the Appwrite Self Hosted docs

Dave
25 Sep, 2023, 23:55

Side note, I'm triggering this function from a localhost:3000 next.js app with executeFunction after I created a magicUrl account and logging in.

The Appwrite client in the client side connects to the configured custom domain (https://backend.xxxxx.com)

Dave
26 Sep, 2023, 00:05

Use case for this Function and accessing the Appwrite user preferences is:

  1. Get all the user preferences in the Function
  2. If there is not a Stripe ID saved in the Appwrite user preferences, call the Stripe API without an ID and let Stripe create one
  3. If there is a Stripe ID saved in the Appwrite user preferences, call another Stripe API with the ID that we saved previously
Dave
26 Sep, 2023, 00:22

Good lord, it's solved. The function and Appwrite install was fine.

I checked out the Digital Ocean one-click setup for Appwrite repository and there's a script that adds port 80 and 443 to the UFW rules.

The firewall blocked my Function from accessing Appwrite without throwing.

For anyone going through the same and doesn't want to have gray hair:

Check your firewall rules in the droplet:

TypeScript
ufw status

If it doesn't show 443 and port 80 like this:

TypeScript
22/tcp                     LIMIT       Anywhere                  
2375/tcp                   ALLOW       Anywhere                  
2376/tcp                   ALLOW       Anywhere                  
22/tcp (v6)                LIMIT       Anywhere (v6)             
2375/tcp (v6)              ALLOW       Anywhere (v6)             
2376/tcp (v6)              ALLOW       Anywhere (v6) 

Update the firewall rules by running:

TypeScript
ufw allow ssh
ufw allow 80
ufw allow 443

ufw --force enable

Double check if the rules are active now:

TypeScript
22/tcp                     ALLOW       Anywhere                  
2375/tcp                   ALLOW       Anywhere                  
2376/tcp                   ALLOW       Anywhere                  
80                         ALLOW       Anywhere                  
443                        ALLOW       Anywhere                  
22/tcp (v6)                ALLOW       Anywhere (v6)             
2375/tcp (v6)              ALLOW       Anywhere (v6)             
2376/tcp (v6)              ALLOW       Anywhere (v6)             
80 (v6)                    ALLOW       Anywhere (v6)             
443 (v6)                   ALLOW       Anywhere (v6)
Dave
26 Sep, 2023, 00:24

[SOLVED] Function always returns "Execution timed out." on self-hosted Appwrite

Dave
26 Sep, 2023, 00:33

Hindsight: I wanted to install a fresh 1.4.3, that's why I didn't use the one-click Appwrite on Digital Ocean. But I see it's now always using the latest version.

Drake
26 Sep, 2023, 01:26

If there is not a Stripe ID saved in the Appwrite user preferences, call the Stripe API without an ID and let Stripe create one

btw...i'd be wary of saving the stripe ID in user preferences because the user is freely able to update user preferences 👀

Dave
26 Sep, 2023, 02:32

Thanks Steven, good tip.

Reply

Reply to this thread by joining our Discord

Reply on Discord

Need support?

Join our Discord

Get community support by joining our Discord server.

Join Discord

Get premium support

Join Appwrite Pro and get email support from our team.

Learn more