DEV Community

Cover image for Adding custom Docker containers to Appwrite
Alex Weininger for Streamlux

Posted on • Updated on

Adding custom Docker containers to Appwrite

In my second post to dev.to, I'll describe how you can add your very own Docker containers to Appwrite!

Intro

While exploring different backend infrastructure options at Streamlux, we decided it would be best to add our own containers to the Appwrite Traefik network. This way we could host completely custom web servers on the same machine as Appwrite. Allowing for extremely low latency between the server and Appwrite, and allowing us to have 100% flexibility in terms of API.

If you haven't heard of Appwrite, taken from Appwrite.io:

Appwrite is a self-hosted solution that provides developers with a set of easy-to-use and integrate REST APIs to manage their core backend needs.

From one developer to another, check it out, it's awesome!

Now let's get back to the task at hand. Adding custom Docker containers to Appwrite is relatively straight forward. However, if you're new to Docker or Traefik it can be a bit daunting.

The majority of the changes we have to make will be to the docker-compose.yml file located in the folder where Appwrite has been installed. For me it was in a folder named appwrite.

Next, I'll go into a little more detail on the changes we will be making to the docker-compose.yml file, but feel free to skip the background section and get right into the changes.

Background

The docker-compose.yml file handles the startup of all the Docker containers Appwrite consists of. Appwrite uses Traefik as a reverse proxy to route incoming network requests to the correct containers.

When adding our own container, we usually want to be able to handle incoming network requests. To tell Traefik we want requests that are pointed to a specific endpoint like www.mydomain.com/customApi to be routed to our container.

Changes

The first change will be at the very top of your docker-compose.yml file.

Change providers.docker.exposedByDefault from false to true as shown below.

version: '3'

services:
  traefik:
    image: traefik:2.3
    container_name: appwrite-traefik
    command:
      - --providers.file.directory=/storage/config
      - --providers.file.watch=true
      - --providers.docker=true
      - --providers.docker.exposedByDefault=true # default is false, change it to true
Enter fullscreen mode Exit fullscreen mode

Next, your appwrite service labels section needs to be updated to include:

- "traefik.enable=true"
- "traefik.constraint-label-stack=appwrite"
- "traefik.docker.network=appwrite"
- "traefik.http.services.appwrite-service.loadbalancer.server.port=80"
# http
- traefik.http.routers.appwrite-http.entrypoints=web
- traefik.http.routers.appwrite-http.rule=PathPrefix(`/`)
- traefik.http.routers.appwrite-http.service=appwrite-service
# https
- traefik.http.routers.appwrite-https.entrypoints=websecure
- traefik.http.routers.appwrite-https.rule=PathPrefix(`/`)
- traefik.http.routers.appwrite-https.service=appwrite-service
- traefik.http.routers.appwrite-https.tls=true
Enter fullscreen mode Exit fullscreen mode

So the appwrite service will now look like this (leave everything after the labels section as it is).

  appwrite:
    image: appwrite/appwrite:0.8.0
    container_name: appwrite
    restart: unless-stopped
    networks:
      - appwrite
    labels:
        - "traefik.enable=true"
        - "traefik.constraint-label-stack=appwrite"
        - "traefik.docker.network=appwrite"
        - "traefik.http.services.appwrite-service.loadbalancer.server.port=80"
        #http
        - traefik.http.routers.appwrite-http.entrypoints=web
        - traefik.http.routers.appwrite-http.rule=PathPrefix(`/`)
        - traefik.http.routers.appwrite-http.service=appwrite-service
        # https
        - traefik.http.routers.appwrite-https.entrypoints=websecure
        - traefik.http.routers.appwrite-https.rule=PathPrefix(`/`)
        - traefik.http.routers.appwrite-https.service=appwrite-service
        - traefik.http.routers.appwrite-https.tls=true
Enter fullscreen mode Exit fullscreen mode

That's all the changes we have to make to the appwrite configuration. Now we can add our own service.

Here is an example Node.js service definition:

  appwrite-customApi:
    image: "node:12-alpine"
    restart: unless-stopped
    labels:
        - "traefik.http.middlewares.portainerpathstrip.stripprefix.prefixes=/customApi/" # requests to this endpoint will be routed to our container 
        - "traefik.http.middlewares.portainerpathstrip.stripprefix.forceSlash=false"

        - "traefik.enable=true"
        - "traefik.constraint-label-stack=appwrite"
        - "traefik.docker.network=appwrite"
        - "traefik.http.services.appwrite-customApi.loadbalancer.server.port=8081"
        - "traefik.http.routers.appwrite-customApi-http.middlewares=portainerpathstrip"
        - traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto = https
        - traefik.http.routers.appwrite-customApi-http.entrypoints=web
        - traefik.http.routers.appwrite-customApi-http.rule=PathPrefix(`/customApi`)
        - traefik.http.routers.appwrite-customApi-http.service=appwrite-customApi
        - "traefik.http.routers.appwrite-customApi-https.middlewares=portainerpathstrip"
        - traefik.http.routers.appwrite-customApi-https.entrypoints=websecure
        - traefik.http.routers.appwrite-customApi-https.rule=PathPrefix(`/customApi`)
        - traefik.http.routers.appwrite-customApi-https.service=appwrite-customApi
        - traefik.http.routers.appwrite-customApi-https.tls=true

    # customize the following properties based on your docker container

    user: "node"
    working_dir: /home/node/app
    environment:
        - NODE_ENV=production
        - PORT=8081
    volumes:
        - ../customApi/:/home/node/app
    command: "npm run prod"
    networks:
        - appwrite
Enter fullscreen mode Exit fullscreen mode

You should be able to copy and paste this, and then change the properties to be able to start your container properly. One thing to note is the port. I have it running on port 8081, so when starting your web server in your container you should start it on port 8081. If you change the port, make sure you change it in all the places it's references in the docker-compose.yml file.

After you're done, you can run docker-compose up -d to restart the docker containers that have had configuration changes.

You can run docker ps to view the containers and make sure your new container has started.

Run docker logs [CONTAINER NAME] to view the logs from your container.

Finishing notes

I would love to know if you have success adding your own container to the Appwrite Traefik proxy. It would be awesome to compile some example docker-compose.yml files to make it easier for other users.

Please reach out to me with any questions you have or things I missed!

Credits

First and foremost I have to give credit to Appwrite. And specifically the absolutely amazing Appwrite team. Go check them out and show your support for their awesome work.

Streamlux

And finally, if you found this post helpful, I am posting today on behalf of my company Streamlux. After months of hard work we've recently released a public beta of our desktop app. If you are a Twitch streamer or viewer come check out what we have in store.

Discussion (4)

Collapse
javibonilla profile image
Javier Bonilla

Alex, thanks a lot for this, it is really useful!

Thanks to your help I managed to get this working for an API in Python Flask. This is the code for a simple example.

Python Flask API code - api.py

from flask import Flask
api = Flask(__name__)

SERVER_HOST = '0.0.0.0'
SERVER_PORT = 8081


@api.route('/')
def hello():
    return "Hello World!"


# Execute server only for development
if __name__ == '__main__':
    api.run(host=SERVER_HOST, port=SERVER_PORT, debug=True)
Enter fullscreen mode Exit fullscreen mode

Get your installed packages in requirements format (requirements.txt) using pip freeze.

Dockerfile - dockerfile

# syntax=docker/dockerfile:1

FROM python:3.9.5-slim

WORKDIR /api

# Copy and install Python requirements and web server
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
RUN pip install gunicorn

# Copy files
COPY api.py .

# Web server port
ENV PORT 8081
EXPOSE $PORT

# Execute web server
CMD exec gunicorn --bind 0.0.0.0:$PORT \
    api:api
Enter fullscreen mode Exit fullscreen mode

Docker compose file - docker-compose.yml

The docker compose remains the same, the only changes are for the custom service definition (only the networks configuration is needed) and don't forget to set your Docker image name: YOUR_IMAGE_NAME.

  appwrite-customApi:
    image: "<YOUR_IMAGE_NAME>"
    restart: unless-stopped
    labels:
        - "traefik.http.middlewares.portainerpathstrip.stripprefix.prefixes=/customApi/" # requests to this endpoint will be routed to our container 
        - "traefik.http.middlewares.portainerpathstrip.stripprefix.forceSlash=false"

        - "traefik.enable=true"
        - "traefik.constraint-label-stack=appwrite"
        - "traefik.docker.network=appwrite"
        - "traefik.http.services.appwrite-customApi.loadbalancer.server.port=8081"
        - "traefik.http.routers.appwrite-customApi-http.middlewares=portainerpathstrip"
        - traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto = https
        - traefik.http.routers.appwrite-customApi-http.entrypoints=web
        - traefik.http.routers.appwrite-customApi-http.rule=PathPrefix(`/customApi`)
        - traefik.http.routers.appwrite-customApi-http.service=appwrite-customApi
        - "traefik.http.routers.appwrite-customApi-https.middlewares=portainerpathstrip"
        - traefik.http.routers.appwrite-customApi-https.entrypoints=websecure
        - traefik.http.routers.appwrite-customApi-https.rule=PathPrefix(`/customApi`)
        - traefik.http.routers.appwrite-customApi-https.service=appwrite-customApi
        - traefik.http.routers.appwrite-customApi-https.tls=true

    # customize the following properties based on your docker container

    # user: "node"
    # working_dir: /home/node/app
    # environment:
    #   - NODE_ENV=production
    #   - PORT=8081
    # volumes:
    #    - ../customApi/:/home/node/app
    # command: "npm run prod"

    networks:
        - appwrite
Enter fullscreen mode Exit fullscreen mode
Collapse
alexweininger profile image
Alex Weininger Author

👏 🎉 👏 that is awesome!

I'm super happy you were able to get it working and that my post was helpful!

Collapse
basats7t profile image
basatS7T • Edited on

Just tried this guide. Works great. And really, really helpful b/c Appwrite does not cover everything and we need to add services as basic as a simple web server, even if just to serve static documents/pages without hitting cross-origin/cross-domain issues, consolidate services on the same domain/IP/server addresses, use the same SSL certificates, etc -- all of which cost more time and money when they proliferate. So, this is a big deal. I had had enough of jumping through hoops with proxies, browser-side tricks. This also allows for better docker-side consolidation. Now traefik presents all of it as one server/service on different paths, ports, etc

So I have one question/request and one recommendation:

My only recommendation is to mention that the port (8081 in the guide) is not the only (big/obvious) thing to watch for. Entrypoints are the other big thing that can vary from Appwrite installation to installation. I notices my setup was very similar to your guide, not the same. I surmise the difference comes from older Appwrite deployments (2-3 upgrades ago). So, the entrypoints may vary and need to be watched for. This is not a biggie for those with some traefik, docker-composer and Appwrite experience. Still, it may help newbies and save head-scratching time for even more experienced persons.

My one question/request is regarding enabling Traefik's dashboard. The simple, generic api commands that used to work no longer turn the trick. Is there some nuance that comes into play with the Appwrite configuration? For example, do we need to explicitly add rules for "/dashboard/" or "/traefik/" or the IP/port/protocol the dashboard uses?

Thanks again for the guide. Regards.

Collapse
basats7t profile image
basatS7T

Never mind, I managed to get traefik dashboard to work. So I'm good. Thanks