[Blog](https://deploywithox.com/blog) [Guides](https://deploywithox.com/blog#c-guides) # How to self-host Dify 1.17.1 on your own VPS, no Docker Run Dify 1.17.1 on your own Ubuntu server with ox: a Flask API, Celery workers, a Next.js app, a plugin daemon, Postgres and Redis, from the real config. Saurav Sharma Oct 17, 2026 13 min read On this page - [What you need](https://deploywithox.com/blog/self-host-dify#what-you-need) - [The steps](https://deploywithox.com/blog/self-host-dify#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-dify#config) - [Postgres, Redis and plugins](https://deploywithox.com/blog/self-host-dify#services) - [Updates and backups](https://deploywithox.com/blog/self-host-dify#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-dify#faq) To self-host Dify you need an Ubuntu server, a fork of the Dify repo with one `ox.toml` and four small files added, and a variables file with 45 values. In our run the first deploy took about 15 minutes, and ox set up two Postgres databases and Redis for it. **How we tested** On Oct 17, 2026, ox deployed Dify 1.17.1 from commit [`8387590a`](https://github.com/langgenius/dify/commit/8387590ace4a094de812b7847fc6a4c3a27cd52b) on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory. The run then redeployed it, backed it up and rebooted the server. Then a separate test agent, one that did not write the config, signed in, made a knowledge base from a text file and got the file's matching passage back from a retrieval test, and checked the app's services and its backup. [Dify](https://github.com/langgenius/dify) is an open-source platform for building apps on language models. In the browser you design chatbots, agents and workflows, connect model providers, and add a Knowledge base, your documents split and indexed so an app can search them. The code is a Next.js web app, a Flask API, Celery processes for background jobs and a Go plugin daemon, on Postgres and Redis. The license is Dify's own, Apache 2.0 plus extra conditions. A multi-tenant service without Dify's written permission, or a web front end with the logo and copyright removed, needs a commercial license. Read `LICENSE` first. ox deployed Dify 1.17.1 (commit 8387590a) on Oct 17, 2026 on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory; the first deploy took 15 minutes and a redeploy took 11 minutes. - **First deploy:** 15 minutes, from adding the project until the app passed the run's first checks. - **Redeploy of the same commit:** 11 minutes, until the app passed the checks again. - **Server:** DigitalOcean, 8 vCPUs and 16 GB of memory (`s-8vcpu-16gb`), with this app alone on it. Every one of the 85 apps we deployed ran on this one size, so it was not picked for this app. - **Memory in use after the reboot:** 2051 MB of 15986 MB, for the whole server: Ubuntu, the app, its services, Caddy and the ox agent. On a server with no apps, ox's tests on real servers hold the agent to 40 MB or less and Caddy to 50 MB or less. - **Services ox runs for it:** `plugin` (postgres), `postgres`, `redis`. - **Background processes:** `api`, `beat`, `plugin-daemon`, `worker`, each its own systemd unit beside the web process. - **Source:** [github.com/langgenius/dify](https://github.com/langgenius/dify) at 1.17.1, commit [`8387590a`](https://github.com/langgenius/dify/commit/8387590ace4a094de812b7847fc6a4c3a27cd52b). The Dify login page The Dify home page with its workflow templates after signing in as the admin A Dify knowledge base with the imported text file listed as Available ## What you need - An Ubuntu LTS server with root access. The facts list shows the size the run used. - A GitHub account to fork `langgenius/dify`. Signing in to ox with GitHub opens your ox account. - An email address and a password for the first admin. Dify wants letters and numbers, at least 8 characters. - A model provider's API key, if you want apps to chat. The run set up no provider and no mail, so chat, agents, High Quality indexing and email were not tested. ## The steps In the ox dashboard, press Add a server and run the install command it prints, as root, on your server. It installs only the ox agent and Caddy. Fork `langgenius/dify` and check out commit `8387590a`, the 1.17.1 code the run deployed. Put the `ox.toml` shown below at the root and the four files under `ox/`, then commit and push. Write `dify.env` on your laptop with the 45 keys from the variables section, plus an `unset KEY` line for every other key the review asks about. Dify's example files list over 200 more, for storage, vector database, mail and monitoring backends this deploy does not use. With the [ox CLI](https://deploywithox.com/docs/cli) signed in, run: Shell ```bash ox new /dify ox review dify --from-file dify.env --wait ``` The project takes the repo's name. The review saves your values and starts the first deploy, and `--wait` prints the log. `ox review dify` on its own lists every key it asks for. When the deploy is green, open the project's address and sign in at `/signin` with the admin email and password. The `/install` page only sends you on to `/signin`, because the admin already exists. The test agent then opened Knowledge, pressed Create Knowledge, chose Import from file and uploaded a small text file. Under Index Method it picked Economical, which uses keywords and spends no tokens. After Save & Process the document reached Available within seconds, which is the Celery worker's job. Retrieval Testing then returned the file's matching chunk for a couple of its words. The run served Dify on its preview address only. A domain of your own was not part of it, and the [domains page](https://deploywithox.com/docs/operate/domains) covers how ox adds one. ## The ox.toml, line by line TOML ```toml # Dify 1.17.1 on ox: the processes of docker/docker-compose.yaml without Docker. # [app] web: the Next.js standalone server (web/Dockerfile, web/docker/entrypoint.sh) # api the Flask API and its socket.io (api/docker/entrypoint.sh MODE=api), at the paths nginx sends it # worker, beat Celery (MODE=worker, MODE=beat) # plugin-daemon langgenius/dify-plugin-daemon 0.6.10 (the compose image's tag), built from source # Not run: nginx (ox's proxy routes the paths below), ssrf_proxy, sandbox, # agent_backend and local_sandbox (Docker or root only; Dify works without them, # the Code node and Agent v2 aside). # The first admin: setup_admin.py makes it in migrate, as /install's form does. required_variables = ["DIFY_ADMIN_EMAIL", "DIFY_ADMIN_PASSWORD"] # api/Dockerfile's apt packages: gmpy2's headers, CJK fonts, mime types, libmagic. packages = ["libgmp-dev", "libmpfr-dev", "libmpc-dev", "fonts-noto-cjk", "media-types", "libmagic1t64"] [app] start = "exec bash ox/web.sh" [build] # web/Dockerfile's workspace install, then api/Dockerfile's venv. install = "pnpm install --frozen-lockfile --ignore-scripts && cd api && uv sync --frozen --no-dev --no-editable" commands = [ # web/Dockerfile: next build with its heap and its skipped deps check, and # the standalone layout its production stage copies. --webpack: on 8 CPUs # Turbopack's build held 13.2 GiB at its peak, past the 12 GB a build gets # on the 16 GB server (killed 3 of 3 times at 12G), and webpack's 6.0 GiB # (measured 2026-10-17). "cd web && NODE_OPTIONS=--max-old-space-size=4096 pnpm_config_verify_deps_before_run=false pnpm build --webpack && cp -r public .next/standalone/web/public && mkdir -p .next/standalone/web/.next && cp -r .next/static .next/standalone/web/.next/static", # api/Dockerfile: the NLTK data and the tiktoken cache the image bakes in (ox/api.sh points at both). # -d names the folder: the Dockerfile makes it first, and without either the # downloader picks ~/nltk_data. The find fails the build if any is missing. "cd api && .venv/bin/python -m nltk.downloader -d \"$PWD/nltk_data\" punkt_tab averaged_perceptron_tagger_eng stopwords && NLTK_DATA=\"$PWD/nltk_data\" .venv/bin/python -c \"import nltk; nltk.data.find('tokenizers/punkt_tab'); nltk.data.find('taggers/averaged_perceptron_tagger_eng'); nltk.data.find('corpora/stopwords')\"", "cd api && TIKTOKEN_CACHE_DIR=\"$PWD/.tiktoken_cache\" .venv/bin/python -c \"import tiktoken; tiktoken.encoding_for_model('gpt2')\"", # The plugin daemon at the commit of its tag 0.6.10, built as docker/local.dockerfile builds it. """ src=$(mktemp -d) git -C "$src" init -q git -C "$src" fetch -q --depth 1 https://github.com/langgenius/dify-plugin-daemon.git 1310a18b2f6bc6f18768a0a6265484830891433c git -C "$src" checkout -q FETCH_HEAD mkdir -p .ox/bin (cd "$src" && go build -ldflags "-X 'github.com/langgenius/dify-plugin-daemon/pkg/manifest.VersionX=0.6.10'" -o "$OLDPWD/.ox/bin/plugin-daemon" ./cmd/server \ && go build -ldflags "-X 'main.VersionX=0.6.10'" -o "$OLDPWD/.ox/bin/plugin-daemon-cli" ./cmd/commandline) rm -rf "$src" """, ] # entrypoint.sh's MIGRATION_ENABLED=true (flask upgrade-db), the plugin daemon's # `commandline migrate`, then the first admin. migrate = "bash ox/api.sh migrate && bash ox/plugin-daemon.sh migrate && bash ox/api.sh admin" [workers] api = { run = "exec bash ox/api.sh api", port = true, health = "/health", paths = ["/console/api", "/api", "/v1", "/openapi", "/files", "/mcp", "/triggers", "/socket.io"] } worker = { run = "exec bash ox/api.sh worker", stop_timeout = "10m" } beat = "exec bash ox/api.sh beat" # Named so ox gives the api PLUGIN_DAEMON_URL, the key it reads. /e is its endpoints' path. plugin-daemon = { run = "exec bash ox/plugin-daemon.sh", port = true, health = "/health/check", paths = ["/e"] } [services] postgres = { extensions = ["vector"] } # Dify's database, and its pgvector store (VECTOR_STORE=pgvector) plugin = { type = "postgres" } # the plugin daemon's own database (compose's dify_plugin) redis = {} [storage] # OPENDAL_FS_ROOT=storage is api/storage (uploads, and each workspace's key with # KEY_PROVIDER_TYPE=local); docker/volumes/plugin_daemon is the daemon's /app/storage. keep = ["api/storage", "docker/volumes/plugin_daemon"] [tools] python = "3.12" # api/pyproject.toml: requires-python ~=3.12.0 uv = "0.8.9" # api/Dockerfile's UV_VERSION go = "1.26" # the plugin daemon's go.mod and its Dockerfile's golang:1.26 ``` The header says what the file is: the processes of Dify's own `docker-compose.yaml`, run without Docker. - `required_variables` names the admin email and password. No example file lists them, and the last migrate step needs both, so ox asks first. - `packages` are the apt packages from `api/Dockerfile`: headers for gmpy2, CJK fonts, the MIME table and libmagic. `[app] start` runs the Next.js server, which answers every path no worker claims. - `install` runs pnpm for the web workspace and `uv sync` for the Python environment. The build commands make the Next.js bundle, with `--webpack` in place of Turbopack, a choice the comment above it ties to build memory. They also fetch the NLTK data and tiktoken cache the API image bakes in, and build the plugin daemon with Go from its tag 0.6.10. - `migrate` upgrades Dify's tables, runs the daemon's own migration, then the admin script. - The `api` worker is the Flask API, with `/health` as its health path. Its `paths` copy the routes in Dify's nginx file that lead to the API, so ox's proxy does nginx's job. `worker` and `beat` are the Celery worker and scheduler. The test agent saw the worker finish an indexing task and beat send a workflow task every minute. `stop_timeout` lets a stop wait ten minutes for a running task. - `plugin-daemon` has a port, `/health/check` as its health path, and `/e` for plugin endpoints. Its name is why ox gives the API the `PLUGIN_DAEMON_URL` it reads. - `[services]` asks for Postgres with `vector`, a second Postgres entry for the daemon's database, and Redis. `[storage]` keeps uploads and the daemon's folder across deploys. `[tools]` pins Python, uv and Go. Left out: nginx, the SSRF proxy, the code sandbox, `agent_backend` and `local_sandbox`. The header says Dify runs without the last four, apart from the Code node and Agent v2. Neither was tested. `ox/api.sh` Code ``` #!/usr/bin/env bash # The api image (api/Dockerfile) in one mode: its environment with /app/api as # the release's api/, then what its entrypoint runs. # api, worker, beat api/docker/entrypoint.sh with that MODE # migrate the entrypoint's MIGRATION_ENABLED step, `flask upgrade-db` # admin the first admin, from DIFY_ADMIN_EMAIL and DIFY_ADMIN_PASSWORD set -eo pipefail cd "$(dirname "$0")/../api" export VIRTUAL_ENV="$PWD/.venv" PATH="$PWD/.venv/bin:$PATH" FLASK_APP=app.py \ NLTK_DATA="$PWD/nltk_data" TIKTOKEN_CACHE_DIR="$PWD/.tiktoken_cache" case $1 in api) export MODE=api DIFY_BIND_ADDRESS=127.0.0.1 DIFY_PORT="$PORT" ;; worker) export MODE=worker ;; # Beat's schedule (a Berkeley DB of each task's last run, celerybeat-schedule # in its cwd by default) lives in the data dir: in the release a deploy # would lose it, and each deploy would restart every interval task's wait. # celery reads CELERY_BEAT_SCHEDULE as `beat --schedule` (auto_envvar_prefix). beat) export MODE=beat CELERY_BEAT_SCHEDULE="$OX_DATA_DIR/celerybeat-schedule" ;; migrate) exec flask upgrade-db ;; admin) PYTHONPATH="$PWD" exec python ../ox/setup_admin.py ;; *) echo "ox/api.sh: unknown mode '$1'" >&2; exit 2 ;; esac unset MIGRATION_ENABLED exec bash docker/entrypoint.sh ``` `ox/plugin-daemon.sh` Code ``` #!/usr/bin/env bash # Dify's plugin daemon (docker-compose.yaml's plugin_daemon) with the compose # file's environment, /app/storage being the kept docker/volumes/plugin_daemon. # `migrate` runs its migrations (the image's `commandline migrate`); with no # argument it serves on $PORT. # DB_* are Dify's own database, so the daemon's come from the `plugin` entry # (compose gives it DB_DATABASE=dify_plugin on the same server). set -eo pipefail cd "$(dirname "$0")/.." storage=$(realpath docker/volumes/plugin_daemon) export PLATFORM=local GIN_MODE=release \ SERVER_HOST=127.0.0.1 SERVER_KEY="$PLUGIN_DAEMON_KEY" \ DIFY_INNER_API_URL="$API_URL" DIFY_INNER_API_KEY="$INNER_API_KEY_FOR_PLUGIN" \ DB_TYPE=postgresql DB_HOST="$PLUGIN_DB_HOST" DB_PORT="$PLUGIN_DB_PORT" DB_USERNAME="$PLUGIN_DB_USERNAME" \ DB_PASSWORD="$PLUGIN_DB_PASSWORD" DB_DATABASE="$PLUGIN_DB_DATABASE" DB_SSL_MODE=disable \ PLUGIN_STORAGE_TYPE=local PLUGIN_STORAGE_LOCAL_ROOT="$storage" PLUGIN_WORKING_PATH="$storage/cwd" \ PLUGIN_INSTALLED_PATH=plugin PLUGIN_PACKAGE_CACHE_PATH=plugin_packages PLUGIN_MEDIA_CACHE_PATH=assets \ PLUGIN_REMOTE_INSTALLING_ENABLED=false FORCE_VERIFYING_SIGNATURE=true \ PYTHON_ENV_INIT_TIMEOUT=120 PLUGIN_MAX_EXECUTION_TIMEOUT=600 MAX_PLUGIN_PACKAGE_SIZE=52428800 \ PYTHON_INTERPRETER_PATH="$(readlink -f api/.venv/bin/python)" UV_PATH="$(command -v uv)" bin=$PWD/.ox/bin # The daemon reads a .env in its folder (godotenv): the storage folder has none. cd "$storage" if [ "${1:-}" = migrate ]; then exec "$bin/plugin-daemon-cli" migrate fi export SERVER_PORT="$PORT" exec "$bin/plugin-daemon" ``` `ox/setup_admin.py` Code ``` """Dify's first admin from DIFY_ADMIN_EMAIL and DIFY_ADMIN_PASSWORD (and DIFY_ADMIN_NAME), made by the service /install's form calls (POST /console/api/setup), so the first visitor cannot claim the instance. Safe to rerun: once Dify is set up it changes nothing.""" import os from app import app from extensions.ext_application_services import application_services from libs.password import valid_password from services.setup_service import SetupAlreadyCompletedError, SetupInput with app.app_context(): email = os.environ["DIFY_ADMIN_EMAIL"].strip().lower() name = os.environ.get("DIFY_ADMIN_NAME") or email.split("@")[0] try: application_services().setup.initialize( SetupInput( email=email, name=name, password=valid_password(os.environ["DIFY_ADMIN_PASSWORD"]), ip_address="127.0.0.1", language="en-US", ), initialization_validated=True, ) except SetupAlreadyCompletedError: print("Dify is already set up: the admin is unchanged.") else: print(f"Made the Dify admin {email}.") ``` `ox/web.sh` Code ``` #!/usr/bin/env bash # The web image (web/Dockerfile's production stage): web/docker/entrypoint.sh's # NEXT_PUBLIC_* exports, then the Next.js standalone server it starts (the # vinext target, off by default, is not built). Bound to 127.0.0.1 on $PORT # behind ox's proxy. set -eo pipefail cd "$(dirname "$0")/.." export NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 HOSTNAME=127.0.0.1 # The collaboration socket is the api's /socket.io on this site. if [ -z "${NEXT_PUBLIC_SOCKET_URL:-}" ]; then if [ "$PUBLIC_SCHEME" = https ]; then scheme=wss; else scheme=ws; fi export NEXT_PUBLIC_SOCKET_URL="$scheme://$PUBLIC_HOST" fi source <(sed '/EXPERIMENTAL_ENABLE_VINEXT/,$d' web/docker/entrypoint.sh) cd web/.next/standalone/web exec node server.js ``` `web.sh` starts the Next.js server with the socket address taken from the project's address. `api.sh` runs the API image's entrypoint in each mode. `plugin-daemon.sh` gives the daemon its compose environment. `setup_admin.py` makes the first admin through the same call as the `/install` form, so no visitor can claim the instance first. Shell ```bash SECRET_KEY CONSOLE_API_URL CONSOLE_WEB_URL SERVICE_API_URL APP_API_URL APP_WEB_URL FILES_URL TRIGGER_URL ENDPOINT_URL_TEMPLATE CONSOLE_CORS_ALLOW_ORIGINS WEB_API_CORS_ALLOW_ORIGINS SERVER_CONSOLE_API_URL DEPLOY_ENV DB_TYPE DB_HOST DB_PORT DB_USERNAME DB_PASSWORD DB_DATABASE REDIS_HOST REDIS_PORT REDIS_PASSWORD REDIS_DB CELERY_BROKER_URL STORAGE_TYPE OPENDAL_SCHEME OPENDAL_FS_ROOT VECTOR_STORE PGVECTOR_HOST PGVECTOR_PORT PGVECTOR_USER PGVECTOR_PASSWORD PGVECTOR_DATABASE PLUGIN_DAEMON_KEY INNER_API_KEY_FOR_PLUGIN PLUGIN_DB_HOST PLUGIN_DB_PORT PLUGIN_DB_USERNAME PLUGIN_DB_PASSWORD PLUGIN_DB_DATABASE MARKETPLACE_URL MARKETPLACE_API_URL DIFY_ADMIN_EMAIL DIFY_ADMIN_NAME DIFY_ADMIN_PASSWORD ``` - `SECRET_KEY` signs session cookies. Dify makes its own when it is empty, and the run set one. `PLUGIN_DAEMON_KEY` and `INNER_API_KEY_FOR_PLUGIN` are secrets the API and the daemon share. Dify's example file ships fixed values for both, so the run set its own. - The address keys, from `CONSOLE_API_URL` to `TRIGGER_URL`, and `CONSOLE_CORS_ALLOW_ORIGINS` are `${PUBLIC_URL}`, a reference ox fills in with the project's address. `ENDPOINT_URL_TEMPLATE` adds `/e/{hook_id}` to it, and `WEB_API_CORS_ALLOW_ORIGINS` keeps Dify's default, `*`. `SERVER_CONSOLE_API_URL` is `${API_URL}`, the API's loopback address. - `DB_*` and `PGVECTOR_*` reference the `postgres` service, and `PLUGIN_DB_*` the `plugin` one. With two Postgres entries, ox cannot guess which one a bare `DB_HOST` means. `REDIS_*` and `CELERY_BROKER_URL` reference Redis. - `STORAGE_TYPE` and `OPENDAL_*` keep uploads on disk, and `VECTOR_STORE` is `pgvector`. `MARKETPLACE_URL` and `MARKETPLACE_API_URL` point at Dify's plugin marketplace, and `DEPLOY_ENV` is Dify's default, `PRODUCTION`. - `DIFY_ADMIN_EMAIL`, `DIFY_ADMIN_NAME` and `DIFY_ADMIN_PASSWORD` are the first admin. See [variables](https://deploywithox.com/docs/operate/variables) for how references work. ## Postgres, Redis and plugins Postgres is shared with other apps on the server, and Dify gets a database and login of its own. Redis runs for this project alone. The run kept vectors in Postgres with pgvector, and other vector stores were not tested. Model providers are plugins that Dify installs from its marketplace. Installing one was not tested. ## Updates and backups To update, merge a newer Dify tag into your fork and push, or run `ox deploy dify --wait`. The run redeployed the same commit, and a newer version was not tested. Before each migrate, ox dumps both databases. Backups need no setup. The run's backups held a dump of each Postgres database and an archive of the kept `api/storage` folder, from Back up now, plus the pre-migrate dumps and a daily Redis copy. `ox backups dify` lists them. A restore was not tested. The [backups page](https://deploywithox.com/docs/operate/backups) covers S3 copies. ## Frequently asked questions ### Do I need Docker to self-host Dify? No. Dify's quick start uses Docker Compose, but each image holds an ordinary program. ox built the Next.js app and the Python API from your fork, compiled the plugin daemon with Go, and ran each as a system service beside Postgres and Redis. ### How do I create the first Dify admin? Set DIFY_ADMIN_EMAIL and DIFY_ADMIN_PASSWORD before the first deploy. The migrate step runs setup_admin.py, which makes the admin through the same service call as Dify's install form, so no visitor can claim the instance. Dify has no open sign-up, and inviting people was not tested. ### Which databases does Dify need? Postgres for Dify's data, with a second Postgres database for the plugin daemon, and Redis for the Celery job queue. Dify's example file defaults to Weaviate for vectors. This run set VECTOR_STORE to pgvector, so the knowledge base's embeddings sit in Postgres and no extra vector database runs. ### Does Dify work without a model API key? Partly. The run set up no model provider, and the knowledge base still worked: a text file indexed with the Economical method, which uses keywords and no model, and Retrieval Testing found it. Chat, agents and High Quality indexing call a model and need a provider and its key. They were not tested. ### How much memory does Dify use? The facts above give what the whole server used after the run's reboot, with Dify, Postgres, Redis, Caddy and the ox agent running. The install also builds the Next.js app and compiles Go on the server. ox runs on any server from 1 GB up and names memory when it is why Dify stopped. New to running apps on your own server? [How ox self-hosts your apps](https://deploywithox.com/self-hosting) shows what ox sets up on your server. On this page - [What you need](https://deploywithox.com/blog/self-host-dify#what-you-need) - [The steps](https://deploywithox.com/blog/self-host-dify#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-dify#config) - [Postgres, Redis and plugins](https://deploywithox.com/blog/self-host-dify#services) - [Updates and backups](https://deploywithox.com/blog/self-host-dify#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-dify#faq) - [Do I need Docker to self-host Dify?](https://deploywithox.com/blog/self-host-dify#faq-docker) - [How do I create the first Dify admin?](https://deploywithox.com/blog/self-host-dify#faq-admin) - [Which databases does Dify need?](https://deploywithox.com/blog/self-host-dify#faq-database) - [Does Dify work without a model API key?](https://deploywithox.com/blog/self-host-dify#faq-model) - [How much memory does Dify use?](https://deploywithox.com/blog/self-host-dify#faq-memory) Written by Saurav Sharma Builds ox. Writes about running apps on servers you own, without Docker and without a platform bill. [Older post How to self-host Docmost on your own VPS without Docker](https://deploywithox.com/blog/self-host-docmost) [Newer post How to self-host diaspora* without Docker on your own VPS](https://deploywithox.com/blog/self-host-diaspora) ## Keep reading - Guides [Self-Hosted Open-Source Apps Deployed with ox: All 85](https://deploywithox.com/blog/self-hosted-open-source-apps-deployed-with-ox) ox deployed 85 open-source apps from pinned commits on fresh servers, with no Docker. The stacks, services, deploy times and a table of every app. Saurav Sharma Oct 17, 2026 16 min read - Guides [How to self-host AFFiNE on your own VPS without Docker](https://deploywithox.com/blog/self-host-affine) Run AFFiNE v0.27.4 on your own Ubuntu server with ox: Postgres with pgvector, Redis, a Rust and Node build, and an admin made from two variables. Saurav Sharma Oct 17, 2026 12 min read - Guides [How to self-host Akaunting on your own VPS without Docker](https://deploywithox.com/blog/self-host-akaunting) Run Akaunting 3.2.4 on your own Ubuntu server with ox: PHP, MariaDB, a scheduler and the first company and admin made on deploy, from the config that ran. Saurav Sharma Oct 17, 2026 10 min read ## Deploy to a server you own ox sets up systemd, Caddy and PostgreSQL on your Ubuntu server and deploys on every push. Free for your first project. [Sign up with GitHub](https://deploywithox.com/login/github) [Read the docs](https://deploywithox.com/docs)