Back
Secure Self-Hosted Infrastructure / Academic Publishing2026 - present

Revista BRASILCON — OJS

OJS 3.4PHPMariaDBDocker ComposeCloudflare TunnelZero-Trust IngressDebianSelf-Hosted
Revista BRASILCON — OJS

Overview

Volunteer migration of the editorial management system of the Revista de Direito do Consumidor — the journal of BRASILCON, the Brazilian Institute of Consumer Policy and Law — to a self-hosted Open Journal Systems (OJS) platform, now live at revistabrasilcon.com. I architected it, run it in production solo, and BRASILCON formally recognized the work in an official letter (Ofício nº 13/2026) signed by its President, the journal's Director-General, and its Secretary-General. ZERO-OPEN-PORT INGRESS — The core security decision: the journal is published to the internet without a single inbound port. Ingress is an outbound-only Cloudflare Tunnel — the cloudflared container dials out to Cloudflare's edge, and all public traffic rides back down that authenticated connection. No port-forwards, no exposed origin IP, no direct attack surface on a residential network. TLS terminates at Cloudflare's edge, which also provides DDoS absorption and traffic filtering for free. DEFENSE IN DEPTH BY ARCHITECTURE — The stack is three Docker containers on an isolated bridge network: a custom-built OJS 3.4 image (configuration and patches baked in at build time — the running container is reproducible, not hand-mutated), MariaDB for the editorial database, and cloudflared. The compose file publishes zero host ports: the database is reachable only by the OJS container on the internal Docker network, and OJS itself is reachable only through the tunnel. Even someone on the LAN cannot talk to the database directly. OPERATIONS — All three containers restart automatically and survive reboots unattended. Day-2 work is mine alone: image rebuilds, DNS, tunnel health monitoring, incident documentation and runbooks. The platform digitizes the journal's full academic workflow — article submission, double-blind peer review, editorial rounds, and publication — for a national legal institute, running on hardware I own.

Implementation

// production excerpt
docker-compose.yml
# Zero published host ports. The journal is reachable ONLY through the tunnel.
services:
  ojs:
    build: ./ojs                       # OJS 3.4 — patches baked in at build time
    depends_on:
      db: { condition: service_healthy }
    networks: [internal]               # no route to the outside world
    restart: unless-stopped
    # NOTE: no `ports:` block — nothing is ever bound to the host.

  db:
    image: mariadb:11
    environment:
      MARIADB_PASSWORD_FILE: /run/secrets/db_pass   # secret, never inline
    secrets: [db_pass]
    networks: [internal]               # reachable only by the ojs container
    volumes: [ojs_db:/var/lib/mysql]
    restart: unless-stopped

  cloudflared:
    image: cloudflare/cloudflared:latest
    command: tunnel --no-autoupdate run
    environment:
      TUNNEL_TOKEN_FILE: /run/secrets/cf_token
    secrets: [cf_token]
    networks: [edge, internal]         # dials OUT to Cloudflare's edge
    restart: unless-stopped

networks:
  edge: {}                             # cloudflared egress only
  internal: { internal: true }         # air-gapped from the public internet

secrets:
  db_pass:  { file: ./secrets/db_pass.txt }
  cf_token: { file: ./secrets/cf_token.txt }

Zero published host ports — ingress is an outbound-only Cloudflare Tunnel.

Official recognition letter from BRASILCON (Ofício nº 13/2026)
Official recognition letter from BRASILCON (Ofício nº 13/2026)