[Blog](https://deploywithox.com/blog) [Guides](https://deploywithox.com/blog#c-guides) # How to self-host solidtime on your own VPS without Docker Run solidtime v0.21.0, an open-source time tracker, on your Ubuntu server with ox: PostgreSQL, a queue worker and a scheduler, from the config that ran. Saurav Sharma Oct 17, 2026 9 min read On this page - [What you need](https://deploywithox.com/blog/self-host-solidtime#what-you-need) - [The steps](https://deploywithox.com/blog/self-host-solidtime#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-solidtime#config) - [PostgreSQL and the units](https://deploywithox.com/blog/self-host-solidtime#services) - [Updates and backups](https://deploywithox.com/blog/self-host-solidtime#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-solidtime#faq) To self-host solidtime you need an Ubuntu server, a fork of the solidtime repo with one `ox.toml` file added, and 59 variables. In our run the first deploy took about 6 minutes, and ox ran PostgreSQL, a queue worker and a scheduler beside the web app. **How we tested** On Oct 17, 2026, ox deployed solidtime v0.21.0 from commit [`b33cf007`](https://github.com/solidtime-io/solidtime/commit/b33cf007888d70856c616e28bfc595eec9422a45) 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, registered through the e-mail link, created a project, ran a timer on it, and saw both after a reload, and checked the app's services and its backup. [solidtime](https://github.com/solidtime-io/solidtime) is an open-source time tracker for freelancers and agencies. You start a timer on a project or add an entry by hand, and the app keeps projects, tasks, clients, billable rates and reports, with several organizations and roles per account. It is a Laravel 12 app with a Vue front end, under the AGPL, and its data lives in PostgreSQL. Importing from Toggl or Clockify was not tried. ox deployed solidtime v0.21.0 (commit b33cf007) on Oct 17, 2026 on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory; the first deploy took 6 minutes and a redeploy took 2 minutes. - **First deploy:** 6 minutes, from adding the project until the app passed the run's first checks. - **Redeploy of the same commit:** 2 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:** 1071 MB of 15986 MB, for the whole server: Ubuntu, the app and its staging copy, 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:** `postgres`. - **Background processes:** `queue`, each its own systemd unit beside the web process. - **Scheduled jobs:** `schedule`. - **Source:** [github.com/solidtime-io/solidtime](https://github.com/solidtime-io/solidtime) at v0.21.0, commit [`b33cf007`](https://github.com/solidtime-io/solidtime/commit/b33cf007888d70856c616e28bfc595eec9422a45). The solidtime log-in page The solidtime dashboard after logging in The time entry Homepage wireframes on the project Ox Verify Website Redesign ## What you need - An Ubuntu LTS server. The run used the size in the facts above and measured the memory there. - A GitHub account to fork `solidtime-io/solidtime`, and an ox account, which you open by signing in with GitHub. - An SMTP server. solidtime mails a link to confirm each new account, and its pages stay locked until it is clicked. The run used a test mail catcher, so real SMTP was not tested. ## The steps Open the ox dashboard, press Add a server, and run the command it shows as root on your Ubuntu server. It installs the ox agent and Caddy. Fork `solidtime-io/solidtime` and check out commit `b33cf007`, the run's commit, which solidtime calls v0.21.0. Add the `ox.toml` shown below at the repo's root, then commit and push. Write `solidtime.env` on your laptop with one `KEY=value` line for each key in the variables list below. `APP_KEY` is Laravel's encryption key: `base64:` followed by 32 random bytes in base64, and `openssl rand -base64 32` makes the second half. With the [ox CLI](https://deploywithox.com/docs/cli) signed in, run: Shell ```bash ox new /solidtime ox review solidtime --from-file solidtime.env --wait ``` The review saves your values and starts the first deploy, and `--wait` follows the run to its end. The console has the same steps, in the [quickstart](https://deploywithox.com/docs/quickstart). When the deploy is green, open the address ox shows. solidtime has no default login, so go to `/register`, fill in the form, and open the link it mails. The test agent did that with an `example.com` address, read the link in the mail catcher, created a project, and ran a timer named "Homepage wireframes" on it. After a reload, both were listed. The run used the project's preview address, so a domain of your own was not part of it. The [domains page](https://deploywithox.com/docs/operate/domains) covers adding one. ## The ox.toml, line by line TOML ```toml # solidtime v0.21.0 (Laravel 12, Inertia and Vue, PostgreSQL), as its # operator would write it: ox detects the Composer install, the Vite build, # FrankenPHP and artisan migrate. [app] # The cheap route: it checks neither the database nor the cache. health = "/health-check/up" [build] # solidtime's composer.json pins php 8.3.* and its lock's phpspreadsheet # wants php below 8.5, the only PHP ox serves (M-6a; v0.21.0 is its latest # release). The operator accepts the risk ox check names: Composer ignores # the php version, the rest of the install is ox's detected one. install = "composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-req=php && npm ci" # Passport signs API tokens with a key pair. The stock Docker image takes it # from PASSPORT_PRIVATE_KEY and PASSPORT_PUBLIC_KEY, which an operator makes # with `self-host:generate-keys`; here passport:keys writes the files to # storage/ once, and storage is kept. migrate = "php artisan migrate --force && { test -f storage/oauth-private.key || php artisan passport:keys --no-interaction; }" [services] postgres = {} [workers] # QUEUE_CONNECTION=database: mails, exports and imports run here. queue = "php artisan queue:work --tries=3 --max-time=3600" [cron] # Laravel's scheduler: reminder mails, the database consistency check. schedule = { schedule = "* * * * *", run = "php artisan schedule:run --no-interaction" } [storage] # Laravel writes storage/ (logs, the file cache, the Passport keys, uploads). keep = ["storage"] ``` ox already recognizes a Laravel app: it serves `public` with FrankenPHP and runs `vite build` from `package.json`. The file adds what solidtime needs on top. - `health` is solidtime's own `/health-check/up`. It answers a small JSON success and checks neither the database nor the cache, and solidtime skips its trusted-host check on that path. ox moves traffic to a new release only once the path answers, and waits up to two minutes. - `install` is the install ox would detect, plus `--ignore-platform-req=php` and `npm ci`. solidtime's `composer.json` requires PHP `8.3.*` and a locked package allows only PHP below 8.5, while ox serves 8.5 and no other version. `ox check` stops on that until the flag is in the line. The run's checks passed on PHP 8.5 this way, and PHP 8.3 was not tested. - `migrate` builds the schema in PostgreSQL, then makes the Passport key pair if `storage/oauth-private.key` is missing. Passport signs API tokens with it, and `storage` is kept, so the keys are made once. API tokens were not tested. - `postgres = {}` asks for a PostgreSQL database for this project. - The `queue` worker runs `queue:work` on the database queue. Two jobs recalculate the tracked time of a project and of a task after entries change, and invitation, timer reminder and token expiry mail is queued too. `--tries=3` tries a failing job three times, and `--max-time=3600` ends the worker after an hour so ox starts it again. The test agent saw the project job logged as done. The sign-up link is sent by the web process. - The `schedule` job runs `php artisan schedule:run` every minute, and Laravel decides what is due. Every ten minutes solidtime looks for timers left running and tokens near expiry, and twice a day it checks for a new version and sends usage counts to solidtime's servers. A consistency check is off unless its variable turns it on. The test agent saw the scheduler tick every minute. - `keep = ["storage"]` names the folder Laravel writes: logs, the file cache, the Passport keys and, on the local disk, uploads and exports. A PHP release is read-only while it runs, so ox links `storage` to the project's data folder, which survives deploys. Shell ```bash APP_NAME APP_ENV APP_KEY APP_DEBUG APP_URL APP_FORCE_HTTPS TRUSTED_PROXIES APP_ENABLE_REGISTRATION SUPER_ADMINS PAGINATION_PER_PAGE_DEFAULT LOG_CHANNEL LOG_DEPRECATIONS_CHANNEL LOG_LEVEL DB_CONNECTION DB_HOST DB_PORT DB_DATABASE DB_USERNAME DB_PASSWORD DB_TEST_HOST DB_TEST_PORT DB_TEST_DATABASE DB_TEST_USERNAME DB_TEST_PASSWORD BROADCAST_DRIVER CACHE_DRIVER QUEUE_CONNECTION SESSION_DRIVER SESSION_LIFETIME MAIL_MAILER MAIL_HOST MAIL_PORT MAIL_USERNAME MAIL_PASSWORD MAIL_ENCRYPTION MAIL_FROM_ADDRESS MAIL_FROM_NAME MAIL_REPLY_TO_ADDRESS MAIL_REPLY_TO_NAME FILESYSTEM_DISK PUBLIC_FILESYSTEM_DISK S3_ACCESS_KEY_ID S3_SECRET_ACCESS_KEY S3_REGION S3_BUCKET S3_URL S3_ENDPOINT S3_USE_PATH_STYLE_ENDPOINT PASSPORT_PERSONAL_ACCESS_CLIENT_ID PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET AUDITING_ENABLED TELESCOPE_ENABLED GOTENBERG_URL OCTANE_SERVER NGINX_HOST_NAME NETWORK_NAME FORWARD_DB_PORT VITE_HOST_NAME VITE_APP_NAME ``` Fifty-eight of the 59 keys are in solidtime's `.env.example`, and the other is `TRUSTED_PROXIES`. `APP_ENV` is `production`, `APP_DEBUG` is `false`, and `APP_URL` is `${PUBLIC_URL}`, a reference ox fills with the project's address (see [variables](https://deploywithox.com/docs/operate/variables)). solidtime refuses requests whose host is not that address or a subdomain of it, unless `TRUSTED_HOSTS` lists it. `TRUSTED_PROXIES` is `127.0.0.1`, so Laravel believes the `X-Forwarded-Proto` header from ox's proxy and builds https links. `APP_ENABLE_REGISTRATION` is `on`, and the example also lists `off` and `invite-only`. While it is on, anyone who can reach the address can register, and changing it was not tested. `SUPER_ADMINS` lists emails that get the admin pages once confirmed, which the run did not test. `DB_CONNECTION` is `pgsql` and the other `DB_` keys are references to the postgres service, such as `${postgres.host}` ([config](https://deploywithox.com/docs/config) lists them), and `DB_TEST_*` are empty. `CACHE_DRIVER` is `file`, and `SESSION_DRIVER` and `QUEUE_CONNECTION` are `database`, so there is no Redis. `LOG_CHANNEL` is `stderr`, so logs reach the journal that `ox logs` reads. The `MAIL_*` keys point at the mail catcher. The example sends files to an S3 bucket. The run used the `local` and `public` disks instead, left the S3 credentials and bucket empty, and did not test S3. `GOTENBERG_URL` is empty: PDF export needs a Gotenberg server, the run had none, and PDF export was not tested. The Passport client keys and the dev-only keys are empty. ## PostgreSQL and the units solidtime needs PostgreSQL and no other service. ox runs one PostgreSQL for the server and gives this project its own database and role. Browse it in the Services tab or with `ox explore solidtime postgres`. A staging copy ran beside production, so the memory in the facts covers both. ## Updates and backups To move to a newer release, merge it into your fork and push, or run `ox deploy solidtime --wait`. ox builds it beside the running one, runs `migrate`, and switches once the health path answers. It dumps PostgreSQL before the migrate, and the run's redeploy left such a dump. A newer solidtime was not tested. Backups need no setup. The run's backups held a PostgreSQL dump and an archive of the kept `storage` folder, taken daily and again by Back up now. List them with `ox backups solidtime`. They hold no variables, so keep `solidtime.env`. A restore was not tested. The [backups page](https://deploywithox.com/docs/operate/backups) covers your own S3 bucket. ## Frequently asked questions ### Do I need Docker to self-host solidtime? No. The solidtime repo has a production Dockerfile under `docker/prod`, but the app is a plain Laravel project. ox runs Composer and npm on your server, serves the app with FrankenPHP, and keeps PostgreSQL, the queue worker and the scheduler as system services. ### How do I create the first solidtime account? Leave `APP_ENABLE_REGISTRATION` on, open `/register` on the deployed address, and fill in your name, email, password, its confirmation and the terms box. solidtime then mails a link, and its pages stay locked until you click it, so SMTP must work. The run used a mail catcher, so real mail was not tested. ### How much memory does solidtime use? After the run's reboot the whole server used the memory shown in the facts. That counts solidtime and its staging copy, PostgreSQL, Caddy and the ox agent. ox takes any server from 1 GB up and tells you when memory is why an app stopped. ### What is TRUSTED_PROXIES for in solidtime? It names the proxies Laravel believes when they say a request came over https. ox's Caddy calls solidtime from 127.0.0.1, so the run set `TRUSTED_PROXIES=127.0.0.1`. Left empty, Laravel trusts no proxy and builds http links, and a browser refuses to load http scripts and styles on an https page. ### Does self-hosted solidtime send data to its makers? By default its scheduler runs two jobs twice a day. One checks for a new version. The other sends counts, such as users, projects and time entries, with the app's URL and version, to app.solidtime.io. `config/scheduling.php` has a variable for each. The run set neither, and switching them off was not tested. 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-solidtime#what-you-need) - [The steps](https://deploywithox.com/blog/self-host-solidtime#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-solidtime#config) - [PostgreSQL and the units](https://deploywithox.com/blog/self-host-solidtime#services) - [Updates and backups](https://deploywithox.com/blog/self-host-solidtime#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-solidtime#faq) - [Do I need Docker to self-host solidtime?](https://deploywithox.com/blog/self-host-solidtime#faq-docker) - [How do I create the first solidtime account?](https://deploywithox.com/blog/self-host-solidtime#faq-account) - [How much memory does solidtime use?](https://deploywithox.com/blog/self-host-solidtime#faq-memory) - [What is TRUSTED_PROXIES for in solidtime?](https://deploywithox.com/blog/self-host-solidtime#faq-proxies) - [Does self-hosted solidtime send data to its makers?](https://deploywithox.com/blog/self-host-solidtime#faq-telemetry) 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 SonarQube Community Build on your own VPS](https://deploywithox.com/blog/self-host-sonarqube) [Newer post How to self-host SearXNG on your own VPS, no Docker](https://deploywithox.com/blog/self-host-searxng) ## 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)