
Deploy Open Journal Systems | (Just Updated) Academic Journal Publishing With REST API, Admin Password Generated
Open Journal Systems (OJS) academic journal publishing, REST API
Just deployed
/data
mariadb
Just deployed
/var/lib/mysql
Deploy and Host Open Journal Systems on Railway
Built for an LLM agent to deploy and use. The deploy form has no fields to fill: the administrator password and the security salt are generated, the database is wired in, the OJS installer runs by itself on first boot and the config, uploads and database sit on volumes. Open Journal Systems (OJS) is the open-source journal management and publishing system from the Public Knowledge Project: submissions, double-blind peer review, copyediting, production and online publication for academic journals, with a REST API.
About Hosting Open Journal Systems
This template runs two services: ojs (the application on one public domain, with a volume at /data holding config.inc.php, the private files directory and the public directory for journal logos and covers) and mariadb (MariaDB 11.8 with a volume at /var/lib/mysql). Journals, submissions, users and uploaded files survive redeploys.
No default password. Upstream's installer ships with the administrator admin / admin. This template installs OJS on first boot from the generated OJS_ADMIN_PASSWORD, so the account exists only with a 32-character password and the instance never answers to the default one. The wrapper refuses to start without a password of at least 12 characters. The first boot takes about two minutes while the installer creates the schema, so the service has no HTTP healthcheck; wait for the login page to answer 200.
What the wrapper fixes for Railway. Apache is moved onto Railway's injected PORT (upstream listens on 80) and told the request was https when the edge says so, so links and redirects stay on https. config.inc.php is moved onto the volume and linked back in, because upstream keeps it inside the image where a redeploy would lose the installed state. The Host-header allowlist (allowed_hosts) is set to your domain, Railway's healthcheck host and the private name, since OJS answers any other host with 400 Server host not allowed. Sessions are no longer bound to the client IP (session_check_ip off), because Railway's edge address changes per request and would sign every user out on the next click. Railway ignores the upstream image's removal of the extra Apache MPM modules, so the entrypoint resets them to prefork before Apache starts. Upstream is OJS 3.5.0-6, pinned by digest.
Using it. Sign in at https://YOUR_DOMAIN/index.php/index/login as admin with OJS_ADMIN_PASSWORD (the username is fixed, it is not a template variable). No journal exists yet: create the first one from the site administration page or with the API call below, then open https://YOUR_DOMAIN/index.php/JOURNAL_PATH/.
Agent contract
- Public URL:
https://RAILWAY_PUBLIC_DOMAIN(the generated domain of theojsservice; journals live under/index.php/JOURNAL_PATH/, the site-wide REST API under/index.php/index/api/v1) - Private URL:
http://ojs.railway.internal:8080 - Credentials: the variable
OJS_ADMIN_PASSWORDon theojsservice, for the fixed usernameadmin(the username is not a template variable) - Auth: a form login.
GET /index.php/index/loginreturns acsrfToken;POST /index.php/index/login/signInwithusername,password,csrfTokenand a cookie jar answers302and starts a session. Writes to the REST API need that session cookie plus anX-Csrf-Tokenheader, taken from thecsrfTokenin the pageGET /index.php/index/admin/contexts. A write with no session answers403, and so does a write with a wrong token. A login with a wrong password does not redirect and answers200. - Health:
GET /index.php/index/loginreturns 200 (about two minutes after the first deploy starts, while the installer runs)
#!/bin/sh
# needs: RAILWAY_PUBLIC_DOMAIN, OJS_ADMIN_PASSWORD (variables of the ojs service); also curl. Creates one throwaway journal.
B="https://$RAILWAY_PUBLIC_DOMAIN"
J=$(mktemp)
# first boot runs the OJS installer (about two minutes); wait for the login page
n=0; until [ "$(curl -s -o /dev/null -w '%{http_code}' "$B/index.php/index/login")" = 200 ]; do n=$((n+1)); [ "$n" -le 90 ] || break; sleep 5; done
curl -s -o /dev/null -w "health: %{http_code}\n" "$B/index.php/index/login"
T=$(curl -s -c "$J" -b "$J" "$B/index.php/index/login" | sed -n 's/.*name="csrfToken" value="\([^"]*\)".*/\1/p' | head -1)
curl -s -o /dev/null -w "login: %{http_code}\n" -c "$J" -b "$J" -X POST "$B/index.php/index/login/signIn" --data-urlencode "username=admin" --data-urlencode "password=$OJS_ADMIN_PASSWORD" --data-urlencode "csrfToken=$T" --data-urlencode "source="
C=$(curl -s -b "$J" "$B/index.php/index/admin/contexts" | sed -n 's/.*"csrfToken":"\([A-Za-z0-9]*\)".*/\1/p' | head -1)
P="journal$(date +%s)"
D="{\"urlPath\":\"$P\",\"name\":{\"en\":\"Agent Journal\"},\"primaryLocale\":\"en\",\"supportedLocales\":[\"en\"],\"supportedFormLocales\":[\"en\"],\"supportedSubmissionLocales\":[\"en\"],\"contactName\":\"Admin\",\"contactEmail\":\"admin@example.com\"}"
curl -s -o /dev/null -w "write with session: %{http_code}\n" -b "$J" -X POST "$B/index.php/index/api/v1/contexts" -H "X-Csrf-Token: $C" -H "Content-Type: application/json" -d "$D"
curl -s -o /dev/null -w "journal page: %{http_code}\n" "$B/index.php/$P/"
curl -s -o /dev/null -w "write without key: %{http_code}\n" -X POST "$B/index.php/index/api/v1/contexts" -H "Content-Type: application/json" -d "{\"urlPath\":\"x$P\"}"
curl -s -o /dev/null -w "write with wrong key: %{http_code}\n" -X POST "$B/index.php/index/api/v1/contexts" -H "X-Csrf-Token: wrong" -H "Content-Type: application/json" -d "{\"urlPath\":\"y$P\"}"
curl -s -o /dev/null -w "login with wrong password: %{http_code}\n" -X POST "$B/index.php/index/login/signIn" --data-urlencode "username=admin" --data-urlencode "password=${OJS_ADMIN_PASSWORD}x" --data-urlencode "csrfToken=$T" --data-urlencode "source="
rm -f "$J"
A fresh deploy prints health: 200, login: 302, write with session: 200, journal page: 200, write without key: 403, write with wrong key: 403 and login with wrong password: 200.
Why Deploy Open Journal Systems on Railway?
Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.
By deploying Open Journal Systems on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.
- Agent-ready - no deployer fields, a contract block and one script that proves the installer finished, the login, the creation of a journal over the REST API and the refusal of a write with no session or a wrong token.
- No default administrator - upstream's
admin/adminis replaced by a generated password at install time. - Data survives redeploys - the database,
config.inc.php, uploaded files and journal images all sit on volumes. - Pinned - the upstream image is pinned by digest and the wrapper by tag and digest.
- Your journal on your domain - submissions, reviews and published articles stay on infrastructure you control.
Common Use Cases
- Launch an academic journal - run submission, peer review, copyediting and publication for a new open-access journal.
- Host several journals for a university or society - one OJS site, a journal per path, shared users and reviewers.
- Conference proceedings and working papers - publish a series with DOIs, issues and article metadata.
- Agent-driven editorial tooling - give an agent the REST API to create journals, read submissions and report on editorial status.
Dependencies for Open Journal Systems Hosting
- The ojs service and the mariadb service with their volumes, created by the template. A custom domain is optional.
Deployment Dependencies
Template Content
